CI/CD

需求:项目通过分支、tags等触发不同环境的一键部署

触发运行-前提

  1. 首先git代码上传origin要带上编写好的gitlab-ci.yml

  2. 在gitlab的project->Settings->CI/CD->Runners->点击Expand->Shared runners->关闭Enable shared runners for this project, 否则无法会导致gitlab CI无法正常执行,提示The pipeline failed due to the user not being verified

  3. Setting-Genneral-Visibility, project features, permissions,打开CI/CD按钮

  4. Setting-CI/CD-Runners,点击enble for this project启动Available specific runners按钮

先上案例:.gitlab-ci.yml

default:
    image: node:16.14.0

variables:
    GIT_CLEAN_FLAGS: 'none'
    USER: master
    SSH_PORT: 58666
    BASE_JAR_DIR: /data1/artifacts
    BASE_ART_DIR: /data1/artifacts
    BASE_LOG_DIR: /data1/logs

    TEST_HOST_FRONTEND_01: 10.xx.xxx.12
    PROD_HOST_FRONTEND_01: 10.xx.xxx.11

    CI_DEBUG_TRACE: 'false'

    LOG_DIR: '$BASE_LOG_DIR/$CI_PROJECT_NAME'
    APP_DIR: '$BASE_ART_DIR/$CI_PROJECT_NAME'
    VER_DIR: '$BASE_ART_DIR/$CI_PROJECT_NAME/$CI_COMMIT_SHORT_SHA'

    WECOM_ROBOT_KEY: 'xxxx' #企微机器人key

stages:
    - clean
    - install
    - build
    - deploy
    - notify

clean-job:
    stage: clean
    tags:
        - titan
    script:
        - rm -rf node_modules
    only:
        variables:
            - $FORCE_INSTALL == "true"

install-job:
    stage: install
    tags:
        - titan
    script:
        - echo 'ooOoo install'
#        - rm -rf .yarn/cache
#        - ln -s /root/.yarn/berry/cache  .yarn/cache
        - yarn || yarn install
    # refs && changes,并集
    only:
        refs:
            - tags
            - master
            - test
            - /^release\/.*/
            - /^patch\/.*/
            - /^hotfix\/.*/
        # refs&changes取并集
        changes:
            - package.json
            - yarn.lock
            - .yarnrc.yml
            - .gitlab-ci.yml

build-release-job:
    stage: build
    tags:
        - titan
    script:
        - yarn build
    only:
        - test
        - /^release\/.*/
        - /^patch\/.*/
        - /^hotfix\/.*/

build-prod-job:
    stage: build
    tags:
        - titan
    script:
        - yarn build
    only:
        - tags
        
# .隐藏keys
# 跨服务器部署
.deploy-job: &deploy-job
    script:
        - ssh -p ${SSH_PORT} ${USER}@${DEPLOY_SERVER} "mkdir -p $VER_DIR"
        - scp -P $SSH_PORT -r ./dist/www/* $USER@$DEPLOY_SERVER:$VER_DIR
        - ssh -p ${SSH_PORT} ${USER}@${DEPLOY_SERVER} "rm $APP_DIR/current; ln -s $VER_DIR $APP_DIR/current"

deploy-uat-job:
    stage: deploy
    variables:
        ## 定义变量, 该job后面的步骤能使用
        DEPLOY_SERVER: $TEST_HOST_FRONTEND_01
    tags:
        - titan
    only:
        - test
        - /^release\/.*/
        - /^patch\/.*/
        - /^hotfix\/.*/
    <<: *deploy-job

deploy-prod-job:
    stage: deploy
    variables:
        DEPLOY_SERVER: $PROD_HOST_FRONTEND_01
    tags:
        - titan
    only:
        - master
    script:
        # 代码所在服务器部署
        - mkdir -p $VER_DIR
        - cp -r ./dist/www/* $VER_DIR
        - rm -rf $APP_DIR/current
        - ln -s $VER_DIR $APP_DIR/current

# 企微通知
notify-job-when-success:
    stage: notify
    script:
        - |
            curl "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WECOM_ROBOT_KEY}" -H 'Content-Type:application/json' -d "
            {
              \"msgtype\": \"markdown\",
              \"markdown\": {
                \"content\": \"项目名称:${CI_PROJECT_NAME}\n>项目构建结果:<font color=\\\"info\\\">成功</font>\n>本次构建由:${GITLAB_USER_NAME} 触发\n>提交号:${CI_COMMIT_SHA}\n>提交日志:${CI_COMMIT_MESSAGE}\n>构建分支:${CI_COMMIT_REF_NAME}\n>流水线地址:[${CI_PIPELINE_URL}](${CI_PIPELINE_URL})\"
              }
            }"
    tags:
        - titan
    only:
        - tags
        - master
        - test
        - /^release\/.*/
        - /^patch\/.*/
        - /^hotfix\/.*/
    when: on_success

notify-job-when-fail:
    stage: notify
    script:
        - echo 'fail'
        - export USER_MOBILE=$(curl "http://account-prod.internal.biateam.com/services/internal/devops/get-mobile-by-email?email=${GITLAB_USER_EMAIL}")
        - |
            curl "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WECOM_ROBOT_KEY}" -H 'Content-Type:application/json' -d "
            {
              \"msgtype\": \"text\",
              \"text\": {
                \"mentioned_mobile_list\":[\"${USER_MOBILE}\"]
              }
            }"
        - |
            curl "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WECOM_ROBOT_KEY}" -H 'Content-Type:application/json' -d "
            {
              \"msgtype\": \"markdown\",
              \"markdown\": {
                \"content\": \"项目名称:${CI_PROJECT_NAME}\n>项目构建结果:<font color=\\\"warning\\\">失败</font>\n>本次构建由:${GITLAB_USER_NAME} 触发\n>提交号:${CI_COMMIT_SHA}\n>提交日志:${CI_COMMIT_MESSAGE}\n>构建分支:${CI_COMMIT_REF_NAME}\n>流水线地址:[${CI_PIPELINE_URL}](${CI_PIPELINE_URL})\"
              }
            }"
    tags:
        - titan
    only:
        - master
        - test
        - /^release\/.*/
        - /^patch\/.*/
        - /^hotfix\/.*/
    when: on_failure

gitlab-ci.yml属性详解

  1. stages

定义pipeline的全部阶段(stage),阶段内的全部任务并行执行,全部任务成功开始下一阶段任务,任何阶段内的任意job失败都会导致pipeline失败,所有stage,job执行成功后pipeline会显示pass。如果未定义stages,则默认有build、test、deploy三个阶段,如果job中未定义stage属性,则默认为test

注意执行的顺序与编写先后顺序无关

  1. only & except
  • only: 定义在哪个分支或者tag上的修改触发执行;

  • except:定义哪个分支或者tag修改不触发任务的执行。

可以使用正则表达式指定,也可以指定关键字。only和except可同时使用。如果only和except在一个job配置中同时存在,则以only为准,跳过except(从上面示例中得出)。

此四个关键字可以和only、except一起使用:refs、variables、changes、kubernetes
使用only:refs和except:refs关键字来控制何时根据分支名称或管道类型向管道添加作业。

only的refs和changes同时使用时,是并集才触发

如下关键字:

关键字	描述
branches    	## git分支
tags	## tag标签
api	    ## 当管道被第二个管道API触发时(不是触发器API)。
external	## 当使用除GitLab之外的CI服务时。
pipelines	 ## 对于多项目触发器,使用CI_JOB_TOKEN的API创建。
pushes	## 管道是由用户推送git触发的。
schedules	## 计划的pipelines
triggers	## 用于使用触发器令牌创建的管道
web	      ## 在GitLab UI中运行管道按钮
merge_requests	## 合并请求
  1. tags
    tags可以从允许运行此项目的所有Runners中选择特定的Runners来执行jobs。这里的tags是在注册Runner过程中,设置Runner的标签,只有Runner中的有这个标签,这个job才能运行

  2. when: 用于执行失败时或者失败后的任务

可以设置为:

on_success:只有当前面的stages的所有工作成功时才执行。

on_failure:当前面stages中任意一个job失败后执行。

always:无论前面的job状态如何都执行。

manual:手动执行,Gitlab8.10开始引入,手动操作指令是不自动执行的特殊类型的job;它们必须要人为启动。手动操作指令可以从pipeline,build,environment和deployment视图中启动。

  1. variables
    job中可以使用关键字variables来定义job变量。当设置了job级别的关键字variables,他会覆盖全局YAML和顶部预定义的job变量,想要关闭全局变量可以在job中设置一个空数组。在job里面定义的variables可被后面的job使用

  2. 隐藏keys
    Key 是以.开始的,GitLab CI 将不会处理它。你可以使用这个功能来忽略jobs。如:.deploy-job: &deploy-job,deploy-job将会被隐藏,不会执行

  3. Anchors
    yaml有个方便的功能称为“锚”,他可以轻松的在文档中复制内容,Anchors可用于复制或者继承属性,并且是使用隐藏keys来提供模板的完美示例。

&在anchor的名称(job_definition)前设置,<<表示将anchor的内容复制到此处,*包括命名的anchor(job_definition)。但是如果job内有相同的属性,将会覆盖掉anchor的内容


.job_template: &job_definition 
   script:
    - echo "job template"
 
test1:
   <<: *job_definition          
 
test2:
   <<: *job_definition       
   script:
   - echo "test2"
  1. environment
    解决部署环境管理的问题需要使用GitLab CI/CD关键词environment。使用它,开发者可以将一个作业设置为某一环境的部署作业,同一个环境的部署作业会被收集到一起,运行部署作业,或者停止作业都将触发一个钩子。开发者可以自定义执行相关业务逻辑。下图是一个部署环境的管理页面
  • on_stop: 是用于定义一个在移除环境时触发的作业,它的值必须是一个同流水线,同环境的作业名称。表明在通过UI移除部署环境或者自动移除部署环境时 运行配置的作业。
  • auto_stop_in: 配置项用于到期自动移除部署环境,如一天后,一周后
  • action: 配置项是用于定义当期作业是部署环境的动作,有三个值,start 默认值),prepare,stop。
    • start: 表明当期作业是创建一个部署环境
    • prepare: 准备部署环境
    • stop: 停止部署环境 on_stop选择的作业必须配置 action: stop
# 部署test环境,停止环境时运行clean_test_env作业
deploy_test_env:
  script: echo 'deploy test env'
  environment:
    name: test
    url: https://fizzz.blog.csdn.net/
    on_stop: clean_test_env # clean_test_env需配置action:stop

# 部署dev环境,一周后自动停止
deploy_dev_env:
  script: echo 'deploy test env'
  environment:
    name: test
    url: https://fizzz.blog.csdn.net/
    auto_stop_in: 1 week
   
# 停止test环境,停止环境的脚本需自行编写
clean_test_env:
  script: echo 'stop deploy and clean test env'
  when: manual
  environment:
    name: test
    action: stop
  1. when: 用于执行失败时或者失败后的任务,值如下
  • on_success: 只有当前面的stages的所有工作成功时才执行。
  • on_failure: 当前面stages中任意一个job失败后执行。
  • always: 无论前面的job状态如何都执行。
  • manual: 手动执行,需要在gitlab的cicd-pipeline页面手动点击运行job。
  1. artifacts: 被用于在job作业成功后将制定列表里的文件或文件夹附加到job上,传递给下一个job.
job1
  stage: build
  image: node:16
  script:
    - mkdir -p ./artifacts
    - echo "编译环境 ${ENV}"
    - yarn build:${ENV}
    - mv dist ./artifacts
#  after_script:
#    - mv dist ./artifacts
  artifacts:
    name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
    paths:
      - ./artifacts/*
    expire_in: 2 day

job2
  stage: deliver
  image: docker:stable
  before_script:
    - mv ./artifacts/* ./  # 使用job1的artifacts目录
    - ls -alh ./dist/*
  script:
    - export IMAGE=xxxx
    - docker login xxx
    - docker build -t IMAGE .
    - docker push IMAGE

docker+k8s版本gitlab-ci.yml

定义参数:首先在gitlab页面上的-/settings/ci_cd->Variables定义HARBOR_PWD,KUBE_CONFIG两个全局常量,在gitlab-ci.yml文件中调用

variables:
  REPOSITORY: hub.xxx.xxxx.cn
  MODULE: module-web
  MODULE_NAMESPACE: web-namespace
  NAMESPACE_DEV: $MODULE_NAMESPACE
  NAMESPACE_TEST: $MODULE_NAMESPACE
  NAMESPACE_PROD: $MODULE_NAMESPACE
  REPOSITORY_CONTEXT: 11336
  REPOSITORY_PATH: $REPOSITORY/$MODULE_NAMESPACE/$MODULE
  WECOM_ROBOT_KEY: "xxx" #企微机器人

stages:
  - test
  - install
  - build
  - deliver
  - deploy
  - notify

#before_script:
#  - echo 'Coty DBEI'

# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
#cache:
#  key: module-web
#  paths:
#    - $(pwd)/.yarn-cache
#    - node_modules/

######################
#### 基础环境定义  #####
######################
## 开发联调环境QA环境
.if_dev_test: &if_dev_test
#  only:
#    - develop
#    - thin-jar-test
#    - /^release.*$/
#    - /^test.*$/
  environment:
    name: test
# variables定义test环境的变量
  variables:
    ENV: test
    NAMESPACE: $NAMESPACE_TEST
    DEPLOMENT: $MODULE-test
    IMAGE_VERSION: $CI_PIPELINE_ID
.if_dev_test_batch: &if_dev_test_batch
  only:
    - develop
    - thin-jar-test
    - /^release.*$/
    - /^test.*$/
  
## 生产环境
.if_prod: &if_prod
#  only:
#    refs:
#      - tags
#    variables:
#      - $CI_COMMIT_TAG =~ /^v\d+.\d+.\d+-?.*$/
  environment:
    name: prod
  variables:
    ENV: prod
    NAMESPACE: $NAMESPACE_PROD
    DEPLOMENT: $MODULE
    IMAGE_VERSION: $CI_COMMIT_REF_NAME
.if_prod_batch: &if_prod_batch
  only:
    refs:
      - tags
    variables:
      - $CI_COMMIT_TAG =~ /^v\d+.\d+.\d+-?.*$/


######################
#### install:安装依赖 ######
######################
.install: &install
  stage: install
  image: node:16
  script:
    - yarn config set registry https://registry.npm.taobao.org
    - yarn install --registry=https://registry.npm.taobao.org
  # 使用缓存依赖包&上传缓存最新依赖包。pull-push作业在作业开始时下载缓存,并在作业结束时将更改上传到缓存
  cache:
    key: "xxx:node_modules"
    paths:
      - node_modules/
    policy: pull-push

yarn:install for dev&test:
  # 加载变量
  <<: *install
  <<: *if_dev_test
  except:
    - master
  only:
    refs:
      - develop
      - /^release.*$/
      - /^test.*$/
    changes:
      - package.json
      - yarn.lock
      - .yarnrc.yml
      - .gitlab-ci.yml

yarn:install for production:
  <<: *install
  <<: *if_prod
  only:
    refs:
      - tags
    variables:
      - $CI_COMMIT_TAG =~ /^v\d+.\d+.\d+-?.*$/
    changes:
      - package.json
      - yarn.lock
      - .yarnrc.yml
      - .gitlab-ci.yml

######################
#### build:打包 ######
######################
.build: &build
  stage: build
  image: node:16
#  before_script:
#    - yarn config set registry https://registry.npm.taobao.org
#    - yarn install --registry=https://registry.npm.taobao.org
#    - mkdir -p ./artifacts
  script:
    - mkdir -p ./artifacts
    - echo "编译环境 ${ENV}"
    - yarn build:${ENV}
    - mv dist ./artifacts
#  after_script:
#    - mv dist ./artifacts
  artifacts:
    name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}"
    paths:
      - ./artifacts/*
    expire_in: 2 day
  # 使用缓存依赖
  cache:
    key: "xxx:node_modules"
    paths:
      - node_modules/
    policy: pull

yarn:build for dev&test:
  <<: *build
  <<: *if_dev_test
  <<: *if_dev_test_batch
  except:
    - master
#  script:
#    - 'yarn build:test'

yarn:build for production:
  <<: *build
  <<: *if_prod
  <<: *if_prod_batch
#  script:
#    - 'yarn build:prod'

#############################
#### deliver:推送镜像阶段 ####
#############################
.deliver: &deliver
  stage: deliver
  image: docker:stable
  before_script:
    - mv ./artifacts/* ./
    - ls -alh ./dist/*
  script:
    # 打包推送镜像
    - export IMAGE=$REPOSITORY_PATH:$IMAGE_VERSION
    - docker login $REPOSITORY -u 'robot$web-namespace' -p $HARBOR_PWD
    - docker build -t ${IMAGE} .
    - docker push ${IMAGE}

docker:push for dev&test:
  <<: *deliver
  <<: *if_dev_test
  <<: *if_dev_test_batch

docker:push for production:
  <<: *deliver
  <<: *if_prod
  <<: *if_prod_batch

##########################
#### deploy:部署到k8s ####
##########################
.deploy: &deploy
  stage: deploy
  image: hub.xxx.xxxx.cn/library/cli:v0.0.3
  dependencies: []
  before_script:
    - /opt/bin/entry.sh
  script:
    - export IMAGE=$REPOSITORY_PATH:$IMAGE_VERSION;
    # 设置上下文
    - kubectl config use-context $REPOSITORY_CONTEXT
    # 启动:更改镜像版本 Deploy可以在部署新版本数据时,成功启动一个pod,才会下线一个老版本的Pod
    - kubectl -n $NAMESPACE set image deployment.apps/$DEPLOMENT $DEPLOMENT=$IMAGE

kube:deploy to dev&test:
  <<: *deploy
  <<: *if_dev_test
  <<: *if_dev_test_batch

kube:deploy to production:
  <<: *deploy
  <<: *if_prod
  <<: *if_prod_batch
  when: manual

##############################
#### notify:企微通知发布信息 ####
##############################
notify-job-when-success:
  stage: notify
  image: hub.xxx.xxxx.cn/library/cli:v0.0.3
  script:
    - |
      curl "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WECOM_ROBOT_KEY}" -H 'Content-Type:application/json' -d "
      {
        \"msgtype\": \"markdown\",
        \"markdown\": {
          \"content\": \"项目构建结果:<font color=\\\"info\\\">成功</font>\n>本次构建由:${GITLAB_USER_NAME} 触发\n>项目名称:${CI_PROJECT_NAME}\n>提交号:${CI_COMMIT_SHA}\n>提交日志:${CI_COMMIT_MESSAGE}\n>构建分支:${CI_COMMIT_REF_NAME}\n>流水线地址:[${CI_PIPELINE_URL}](${CI_PIPELINE_URL})\"
        }
      }"
  when: on_success
  only:
    - develop
    - test
    - prod
    - thin-jar-test
    - /^release.*$/
    - /^test.*$/
    - tags

notify-job-when-fail:
  stage: notify
  image: hub.xxx.xxxx.cn/library/cli:v0.0.3
  script:
    - export USER_MOBILE=$(curl "http://account-prod.internal.biateam.com/services/internal/devops/get-mobile-by-email?email=${GITLAB_USER_EMAIL}")
    - |
      curl "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WECOM_ROBOT_KEY}" -H 'Content-Type:application/json' -d "
      {
        \"msgtype\": \"text\",
        \"text\": {
          \"mentioned_mobile_list\":[\"${USER_MOBILE}\"]
        }
      }"
  when: on_failure
  only:
    - develop
    - test
    - prod
    - thin-jar-test
    - /^release.*$/
    - /^test.*$/
    - tags

docker+k8s粗略了解

初始化创建dockerfile文件、nginx.conf文件、deployment-dev.yaml和service-dev.yaml

  1. docker: 容器,根据本地目录上Dockerfile文件和nginx.conf文件将dist包成一个容器,推送用于部署,其启动速度快,资源隔离,耗能低,减小系统开销。

  2. Kubernetes: 用于对容器的部署、管理、修复、负载均衡和配置等用途,使得用户可以更加轻松地构建、部署和运行程序。其核心概念有:

  • 创建Namespace: 命名空间,是一个虚拟集群,用于隔离和管理不同的资源。不同命名空间中的资源是相互独立的,同一命名空间中的资源可以直接访问。

  • 部署pod: pod是 k8s中最小的部署单元,由一个或多个容器组成, Pod 提供容器运行时的环境,同时也提供了一些共享的资源,可使用kubectl命令增删改pod。可通过pod ip访问容器nginx

        # 查看pod ip、节点信息
        kubectl get pods -n test -o wide
        curl 10.xxx.xxx.79
        curl 10.xxx.xxx.80
    
  • 部署Deployment: 管理pod,创建、删除、更新pod,以确保 Pod 的副本数始终处于指定状态,用Deployment启动的容器,如果直接删除Pod则会在被删除后自动再次创建pod,只有这个Deployment被删除才能永久删除pod。

  • 部署Service(测试环境): 用于提供对这组 Pod 的访问和负载均衡。Service 可以通过集群内部 IP、DNS 名称或外部 IP(NodePort 或 LoadBalancer)提供服务。如果不指定 service type,则创建的Service默认为ClusterIP,这种方式只能在Pod内部实现访问。指定type为NodePort类型,可以实现Pod外暴露访问。可通过Service IP访问Nginx,且在外部可通过ServiceNodePort访问Pod

        kubectl get svc -n test -o wide
        curl 10.xxx.xxx.62:8888
    
  • 部署Ingress(正式环境): 用于将外部流量路由到集群内部的服务。Ingress 可以基于 URL、域名或 HTTP 头将流量路由到不同的服务中。在外部使用Ingress Host访问Pod。

设置定时任务

如果想要定时任务执行Pipeline, 可以在gitlab的project->CI/CD->Schedules->New schedule中设置,其中时间间隔的设置采用的是corn语法

gitlab-runner常用命令

$ gitlab-runner list   //查看各个runner状态
$ gitlab-runner stop   //停止服务
$ gitlab-runner start   //启动服务
$ gitlab-runner restart   //重启服务
$ gitlab-runner status    //查看服务状态
$ gitlab-runner verify    //检查注册的runner是否可以连接,但不验证gitlab是否正在使用runner
$ gitlab-runner verify --delete --name project1_runner_for_sonar  //删除runner, 这里的project1_runner_for_sonar是注册时填写的gitlab-ci description for this runnergitlab-runner unregister --url https://gitlab.com --token t0k3n    //通过url和token注销一个runner
$ gitlab-runner unregister --url https://gitlab.com --token t0k3n    //通过url和token注销一个runner
$ gitlab-runner unregister --name test-runner     //通过runner名称注销(同名删除第一个)
$ gitlab-runner unregister --all-runners    //注销所有runner

  1. 同一个stage有多个job并行时,会出异步导致找不到文件问题
$ cp -r ./dist/redaviator/* $VER_DIR
cp: cannot stat './dist/redaviator/*': No such file or directory
...

# 解决:
1. 第一种,添加多个stage,作为同步使用
2. 第二种,在同一个job里写2个job的代码

参考资料

Gitlab CI环境搭建和配置

gitlab-ci.yml详解

kubernetes官网

Kubernetes:组件、核心概念和Nginx实战演示

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐