Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

基于GitLab-Runner打造锋利的CI/CD #2

Open
lostvita opened this issue Apr 11, 2019 · 0 comments
Open

基于GitLab-Runner打造锋利的CI/CD #2

lostvita opened this issue Apr 11, 2019 · 0 comments

Comments

@lostvita
Copy link
Owner

lostvita commented Apr 11, 2019

前言

本文介绍的内容包含:

  • 理解CI/CD及其必要性
  • gitlab-runner安装
  • gitlab-ci配置说明
  • ssh免密登录
  • rsync文件部署
  • 多环境发布与回滚
  • 高频出现的问题并解决

阅读本文需要你:

  • 有一定的ssh知识基础,一份SSH操作指南
  • gitlab ci/cd概念基础
  • 花10min左右的时间

CI/CD科普

1. CI与CD

持续集成(Continuous Integration)指开发人员在特性分支(频繁)提交代码,立即执行构建和单元测试,代码通过测试标准后集成到主干的过程。强调的是分支代码的提交、构建与单元测试,这个过程的产出是单元测试报告。
image
说明:这里的 test 是指unit test(图片来源见文末参考链接)

持续交互(Continuous Delivery)是在持续集成的基础上,将构建的代码部署到「类生产环境」,完成QA测试之后手动部署到生成环境的过程。强调代码部署,这个过程产出测试报告。
image
说明:这里的 test 是真的test

持续部署(Continuous Deployment)是持续交互的下一步,强调部署生产环境代码的过程自动化,同时可以处理上线通知等操作。
image
说明:与持续交互主要就是手动跟自动的区别。

2. CI/CD的必要性

以一言概之的话我想应该是:机械的事情让机器做。一个开发团队,没有CI/CD,我想可能是这样子的:无法管理代码多人多地协作(git repository也是CI的一部分),系列的shell需要人工处理,代码的发布需要登录服务器等等;相反,拥有CI/CD,这些事情都交给机器去完成,腾出的碎片时间去做更有意义的事情(比如摸鱼放松下)。

3. 理想的CI/CD开发流应该是怎样的?

我认为理想的CI/CD开发流应该包含三个阶段:build、deploy和notify
build阶段专注做代码构建与单元测试,deploy阶段专注做test/gray/prod环境的代码部署,notify阶段专注做上线通知,如下图;
image

以下内容围绕build和deploy两个阶段完成从0到1的部署。笔者的系统环境:Ubuntu 18.04.1 LTS

Gitlab-Runner安装并注册

  • 安装runner
sudo apt-get install gitlab-runner

官网文档 install gitlab-runner

  • 注册runner
sudo gitlab-runner register

之后是QA式操作,按照提示语输入信息即可,可参考官网操作,需要注意:

  1. gitlab host和token在你的gitlab项目上找,页面路径是:Settings >> CI/CD >> Runners
  2. runner执行器选择 docker,image(镜像)输入 node:8.11.2-stretch

注册成功后,我们就能在:Settings >> CI/CD >> Runners下看到我们注册的runner
image

Gitlab-Runner配置

在项目根目录下新建 .gitlab.yml文件,加入如下内容:

image: node:8.11.2-stretch

cache:
  paths:
    - node_modules/

stages:
  - build
  - deploy

unit_test:
  stage: build
  only: 
    - feature/test
  script: 
    - echo 'Unit testing.....'
  after_script:
    - echo 'Unit test done.'

compile:
  stage: build
  before_script:
    - npm install
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
  only:
    - feature/test
    - tags

deploy_test:
  stage: deploy
  only:
    - feature/test
  dependencies:
    - compile
  environment:
    name: test_env
    url: https://test.example.com
  before_script:
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - eval $(ssh-agent -s)
    - echo "$TEST_KNOWN_HOST" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
    - echo "$TEST_CONFIG" > ~/.ssh/config
    - chmod 600 ~/.ssh/config
    - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-add ~/.ssh/id_rsa
    - command -v rsync || (apt-get -qq update && apt-get -qq install -y rsync)
  script:
    - rsync -rve ssh dist/ user@server_host:project_path/dist
  after_script:
    - echo 'deploy test env done.'
  when:
    on_success

unit_test、compile和deploy_test是自定义的job名字,另外几点配置说明:

  • cache
    cache设置缓存文件,这里缓存node_module依赖包,提高job构建效率,定义在全局,对所有的job生效;
  • stage
    设置build和deploy两个阶段
  • artifacts
    下载文件。定义compile产出的dist文件夹缓存到gitlab服务器,提供下载(gitlab web页面下载)或者在同一个stage的各job之间共享;
  • only
    定义job的触发条件,可以指定分支名、tags(打tag时触发)等(这些条件是或的关系,满足其中一个即触发);
  • when
    定义job的触发时机,值可以为:on_successalwaysmanual等;
  • dependencies
    定义当前job所要依赖的job;
  • environment
    定义当前job所属的环境,对回滚操作非常有用,后面详述;
  • deploy_test这个job的 before_script 有很长一段内容,这里的作用是配置ssh免密登录,后面详述。

ssh免密登录

为什么需要免密登录?

  • 构建产出的dist文件要传输到目标服务器(测试机/生成机),要么基于http网络协议、要么基于ssh协议(或其他文件传输协议?)
  • 基于http需要写文件接收接口,这里直接使用基于ssh传输文件的rsync,简单、安全!
  • runner内定义的一系列script是在一个docker容器内执行的,无法人工干预,那么登录服务器就要做成免密。

先在本机(注册runner所在的机器)配一遍免密登录服务器的流程:
1. 生成一对公私钥:使用rsa作为非对称加密方式

ssh-keygen -t rsa -C "$(whoami)@$(hostname)-$(date -I)"

说明:一路enter就好了,切记 Enter passphrase 时直接enter,这样就是 no passphrase。如果你非要加个password,对不起,没救了!

2. 定义ssh config内容:在~/.ssh/config文件写入以下内容(文件不存在直接创建)

Host any_name
  Port your_port
  HostName server_ip
  User user
  IdentityFile ~/.ssh/id_rsa

说明:定义ssh的config文件是为了快捷访问,就像你配置host一样,没有hostname,你只能访问ip。配置后你就可以通过 ssh any_name 登录服务器了。当然,不出意外,会要求你输入服务器的登录密码。

3. 免密登录
免密登录的精髓就是:把本机的公钥存储到目标服务器的authorized_keys文件内(该文件服务器上不存在可以直接创建。)

ssh-copy-id -i ~/.ssh/id_rsa.pub username@ip

特别地:如果你的端口不是默认的22端口,则加上端口号 -p PORT

4. 验证登录

ssh any_name

不出意外,你应该可以直接登录服务器了。那么,我们回到gitlab的配置上~

gitlab上定义ssh配置信息

我们进入gitlab页面位置:Settings >> CI/CD >> Environment variables下定义 .gitlab.yml 上出现的几个变量:
image

  • SSH_PRIVATE_KEY:把本机(runner所在机器)的私钥复制过来:~/.ssh/id_rsa
  • TEST_CONFIG:把刚才ssh config定义的信息复制过来:~/.ssh/config
  • TEST_KNOWN_HOST:定义这个变量是为了让ssh对服务器进行身份确认(不然会被ssh认为是一个不被信任的环境),变量值使用以下命令生成:
ssh-keyscan -p PORT IP

使用rsync传输文件

rsync -rve ssh dist/ user@hostname:project_path/dist

说明hostname就是你在ssh config定义的Host值。rsync操作指南

定义environment

设置environment的好处是可以对各发布环境进行管理,特别是线上发布,出现bug可以及时操作回滚。
在gitlab web页面位置:Operations >> environments 可以查看当前项目下的environments,点击其右侧的预览按钮即可查看对应环境的发布效果
image
点击其中一个environment:test_env,可以查看当前环境下的所以发布记录,右侧的按钮可以执行回滚操作
image

踩坑小记

在整个搭建过程,很多都是关于ssh登录服务器的问题,择几个高频出现的问题说明下:
1. Host key verification failed
当我们初次使用ssh登录服务器的时候,ssh会要求验证远程服务器的身份,通过身份验证之后才允许连接。解决该问题有两种方式:

  • 设置免身份认证
    在ssh config配置中加一段StrictHostKeyChecking no,如此.gitlab.yml配置中就可以去掉关于known_hosts的设置了;
  • 通过远程服务器的公钥指纹进行身份认证
ssh-keyscan -p PORT IP

将脚本输出的结果保存在~/.ssh/known_hosts文件中(@gitlab上定义ssh配置信息部分有提及),这样ssh在登录之前会从该文件中拿到目标服务器的公钥指纹进行身份确认。

2. Permission denied, please try again
出现这个问题是没有配置「ssh免密登录」,配置操作见@为什么需要免密登录部分

3. rsync: Failed to exec a: No such file or directory
这个问题一般情况下并不会出现,但却是个实实在在的坑。我在deploy的job里面通过rsync将构建生成的dist目录上传至服务器,抛出不存在该目录的错误。我在compile这个job执行后,list下根目录下的文件/夹:
image
可以看见,实实在在是生成了dist目录,但进入下一个job的时候却提示不存在!问题出在:
dist目录并不是由构建直接生成的文件夹,而是release-[timestamp]目录的软链接(笔者用的是Ubuntu,在webpack配置里面设置了个骚操作:每次构建产出一个release-[timestamp]目录,同时建立一个软链。软链不是一个目录,它的内容就是目标文件夹的地址)。
在我的.gitlab.yml配置里面,artifacts缓存的是dist,没有把实际的文件夹release-[timestamp]缓存,那么进入下一个job的时候,自然就提示不存在该目录了。解决办法是:

cp dist public

在compile这个job里面,构建之后复制一份dist目录,再将public目录交由artifacts缓存。

参考文章

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant