Welcome to the K-free blog;|

k-free

园龄:5年8个月粉丝:5关注:7

Jenkins实现CI/CD发布(Ansible/jenkins共享库/gitlab)

Jenkins实现多环境发布

1. 需求介绍

 本人负责公司前端业务模块,由于前端模块较多,所以在编写jenkinsfile时会出现很多项目使用的大部分代码相同的情况,为解决这种问题,采用了jenkins的共享库方式优化,并且jenkins要支持多环境发布,我们有gray与online两个环境,可以确定的是每次gray环境都会优先更新,之后再更online环境;也会有大版本上线需同时更新的情况;有了需求就尽管写代码啦;

发布online环境的话要从已经部署后的gray环境拷贝,尽量做到一次编译,但配置文件各不相同,所以流程如下;

2. 模块介绍

  • jenkins:编译、UI发布;
  • gitlab:配置文件、共享库存储;
  • ansible:playbook方式发布;

3. 共享库建立

  1. 在gitlab上创建空项目并clone到本地(不做演示);
  2. 在jenkins的Manager Jenkins —>Configure System-→Global Pipeline Libraries中配置共享库的git地址;这里不做演示;
  3. 在项目中创建目录结构,如下所示;
shared_library/
├── README.md
├── resources
│   └── org
│       └── devops
├── src
│   └── org
│       └── devops
│           ├── build_deploy.groovy
│           ├── checkout_code.groovy
│           └── email_notification.groovy
└── vars

7 directories, 4 files

2.1 共享库介绍

  • build_deploy.groovy
package org.devops

// 定义编译函数,传入两个参数
// Env: 要发布的环境
// Buildcommand: 编译时的命令,由于多个项目可能编译命令不一样,所以这里以接收参数的方式;
def Build(Env,Buildcommand){
    if (Env == "gray") {
// 需求说明了,如果是灰度环境的话就要拉取源码进行编译,所以这里执行编译命令,并打印当前发布环境;
        sh Buildcommand
        println("gray环境")
// 如果是生产的话则不进行编译,直接从发布后的gray环境中复制即可;具体实现在playbook中;
    } else if(Env == "online"){
        println("生产环境不编译")
// 如果是全部发布的话则跟灰度一个逻辑,不过就是打印的结果不一样,如果打印无所谓的话也可以写到上面条件变成or;
    } else if(Env == "all"){
        sh Buildcommand
        println("全环境发布")
    }
}

// 定义获取配置文件方法,传四个参数
// Env: 要发布的环境;
// project: gitlab对应的项目名称;
// filename: 配置文件的名称;
// path: 配置文件要放到哪个位置(主要是灰度环境用)
def Get_Config(Env,project,filename,path){
    if (Env == "gray") {
// 如果环境是灰度的话;先判断编译目录下是否有"gray_env"目录,有就删除;然后clone项目地址到本地的gray_env目录下,并且将项目中的配置文件挪到编译后的目录中;
        sh "`[ -d ./gray_env ] && rm -rf ./gray_env/ || :` && git clone ssh://git@xxxxx/xxx/${project} gray_env && mv -f gray_env/${filename} ./${path}"
// 如果是生产的话跟上面一样,不过目录名是online_env,将项目中的配置文件挪到编译目录;
    }else if(Env == "online"){
        sh "`[ -d ./online_env ] && rm -rf ./online_env/ || :` && git clone ssh://git@xxxx/xxxxx/${project} online_env && mv -f online_env/${filename} ./"
// 如果是全发布的话则两步都走;
    }else if(Env == "all"){
        sh "`[ -d ./gray_env ] && rm -rf ./gray_env/ || :` && git clone ssh://git@xxxx/xxx/${project} gray_env && mv -f gray_env/${filename} ./${path}"
        sh "`[ -d ./online_env ] && rm -rf ./online_env/ || :` && git clone ssh://git@xxxx/xxxx/${project} online_env && mv -f online_env/${filename} ./"
    }
}

// 定义压缩编译后程序并发到目标服务器上
def compress_copy(Env){
    if (Env == "gray" || Env == "all") {
// 将目录打包名为"jenkins项目的名字".tar.gz文件,并忽略调本地的.svn目录
        sh "tar czf ${JOB_BASE_NAME}.tar.gz --exclude=.svn ./dist"
// 将文件传到发布机器的/tmp/目录下
        sh "scp -P50022  ${JOB_BASE_NAME}.tar.gz sysvideo@$ansible的机器地址:/tmp/"       
    }
}
//定义发布方法,传四个参数
// Env: 要发布的环境;
// file_path: playbook的路径,相对于"/etc/ansible/jenkins"目录的相对路径;
// config_name: 对应的配置文件名称
def Deploy(Env,file_path,config_name){
    if (Env == "gray") {
// 如果是灰度环境的话则跳过playbook中tags为online的步骤进行发布
        println("灰度环境跟以前线上环境发布一样")
        // sh "cd /etc/ansible/jenkins/$(dirname ${file_path}) && sudo ansible-playbook --skip-tags='online' -e update_file=/tmp/${JOB_BASE_NAME}.tar.gz ${file_path}"
    }else if(Env == "online") {
// 如果是online的话则需要先将配置文件拉到本机,也就是ansible这台机器上;随后调用下面的playbook中online的tags进行发布
        sh "sudo ansible-playbook -e config_name='${config_name}' -e job_name='${JOB_BASE_NAME}' /etc/ansible/jenkins/global/pull_config_from_jenkins.yml"
        println("发布生产环境,先把配置文件从jenkins拉到本地")
        // sh "ansible-playbook --tags=online /etc/ansible/jenkins/global/${file_path}"
    }else if(Env == "all") {
        sh "sudo ansible-playbook -e config_name='${config_name}' -e job_name='${JOB_BASE_NAME}' /etc/ansible/jenkins/global/pull_config_from_jenkins.yml"
        println("拉完配置文件之后,执行发布命令,默认会都跑一遍")
        // sh "cd /etc/ansible/jenkins/$(dirname ${file_path}) && sudo ansible-playbook -e update_file=/tmp/${JOB_BASE_NAME}.tar.gz ${file_path}"
    }
}
  • checkout_code.groovy
package org.devops
// 定义获取代码的通用方法,接收一个参数
// address: 代码所在的svn地址,仅支持SVN
def CheckOut_Code(address) {
                checkout (
                    changelog: false,
                    poll: false,
                    scm: [
                        $class: 'SubversionSCM',
                        additionalCredentials: [],
                        excludedCommitMessages: '',
                        excludedRegions: '',
                        excludedRevprop: '',
                        excludedUsers: '',
                        filterChangelog: false,
                        ignoreDirPropChanges: false,
                        includedRegions: '',
                        locations: [
                            [
                                cancelProcessOnExternalsFail: true,
                                credentialsId: 'svn_pass',
                                depthOption: 'infinity',
                                ignoreExternalsOption: true,
                                local: '.',
                                remote: address]
                                ],
                            quietOperation: true,
                            workspaceUpdater: [
                                $class: 'UpdateUpdater']
                        ]
                )
}
  • email_notification.groovy
package org.devops
// 定义通用发送邮件方法,接收一个参数;
// EmailUser: 收件人,默认为xxxx@netxx.com;
def send_mail(EmailUser = 'xxxx@netxx.com') {
    mail (
        subject: "Status of pipeline : ${currentBuild.fullDisplayName}",
        body: "${env.BUILD_URL} has result ${currentBuild.result}",
        to: EmailUser,
        from: "jenkins@jenkins.com"
    )
    println(EmailUser)
}

4. pipeline编写

下面是其中一个实例,其它的可以按照这个模板去修改

// 引用共享库
@Library("shared_library") _
import org.devops.email_notification
def email_notification = new org.devops.email_notification()
def code = new org.devops.checkout_code()
def build_deploy = new org.devops.build_deploy()
pipeline {
        agent none

        tools {
            nodejs "nodejs 14.18.2"
        }
        parameters {
            choice(
                    choices: "gray\nonline\nall",
                    description: "选择发布到哪个环境",
                    name: "environment"
            )
            string(
                    name: "Code_Address",
                    defaultValue: "None",
                    description: "定义代码的SVN路径,默认为None"
            )
            string(
                name: "Config_Name",
                defaultValue: "None",
                description: "前端文件的文件名,如果是gray的可以不用写;"
            )
        }
    stages {
        stage("checkout code") {
            agent {
                label "master"
            }
            steps {
                script {
                    /*
                    1. 拉取代码,从SVN地址获取代码的位置;这里只考虑了SVN的情况,未考虑Git;
                    2. 共享库位置在git,详情咨询kfreesre@163.com;
                     */
                    code.CheckOut_Code("${Code_Address}")
                }
            }
        }
        stage("build and Get Config") {
            agent {
                label "master"
            }
            steps {
                script {
                    /*
                    1. Build将源码进行编译;
                    参数解释:
                        1. environment参数可固定;
                        2. npm config set ...; 代表编译命令,根据实际情况修改;
                    2. Get_Config从Git获取项目相关配置文件,environment参数可固定;
                    参数解释:
                        1. environment参数可固定;
                        2. xxxx: 配置文件所在的git仓库名称;同一项目的灰度与生产名称一致,传入一个就行,可去git确认;
                        3. production.js: 配置文件的名称;
                        4. dist/static/js/: 当源码编译后,会在workspace中生成一个dist的目录,将3中的"production.js"拷贝到编译后的目录中;git上项目的描述中说明了具体位置;
                     */
                    build_deploy.Build("${environment}","npm config set registry 私库地址 && npm install && npm run build")
                    build_deploy.Get_Config("${environment}","xxxx","production.js","dist/static/js/")
                }
            }
        }

        stage("compress and copy") {
            agent {
                label "master"
            }
            steps {
                script {
                    /* 
                    1. 将编译后的dist目录打包为压缩包,命名为当前jenkins项目的名称;
                    2. 随后将打包后的tar.gz文件发送到云视ansible的/tmp目录下;
                    */ 
                    build_deploy.compress_copy("${environment}")
                }
            }
        }

        stage("Deploy") {
            agent {
                label "Deploy"
            }
            steps {
                script {
                    /*
                    参数解释:
                        1. environment可固定,不用修改会自动获取在点击构建时选择的环境;
                        2. xxxx/ngin_update_all.yml是对应的anible yaml文件,这里填写相对路径,相对的是/etc/ansible/jenkins;
                     */
                    build_deploy.Deploy("${environment}","xxxx/update_all.yml","${Config_Name}")
                }
            }
        }
    }
    post {
        always {
            script {
                /* 
                每次都发送邮件,默认发给"xxxx@xxx.com",如果要修改在send_mail()中传参即可,类似 email_notification.send_mail("xxxx.xxx@net.com"),如果有多个收件人可以逗号为分隔符

                */
                email_notification.send_mail("xxxx@netxxx.com,xxxx.xx@netxxx.com")
            }
        }
    }

}

5. playbook编写

  1. 编写online环境需要拉取配置文件的playbook
- hosts: jenkins
  become: yes
  become_method: sudo
  become_user: root

  vars:
  - config_name: None
  - job_name: None
	# 这里写上jenkins工作目录,我这里TEST是jenkins-UI上创建的前端项目所在文件夹,所以固定;
  - config_path: /data/jenkins/workspace/TEST/{{job_name}}
  - local_config_path: None
  
  tasks:
  - name: Pull the configuration file
    fetch:
      src: "{{ config_path }}/{{ config_name }}"
      dest: /tmp/
    register: dest_path
  1. 编写online及gray环境发布的playbook(每个项目所在的目录不一样,所以发布的项目对应的playbook基于这个修改即可)
---
- hosts: 
  - xxxx
  - xxxx
  become: yes
  become_method: sudo
  become_user: root

  vars:
  #  - update_path: /var/www/html/xxxx/pc/dist
   - gray_path: /var/www/html/gray/xxxx/pc/dist
   - online_path: /var/www/html/xxxx/pc/dist
   - update_file: default
# 这里就直接写死了,因为每个项目都对应一个playbook,不过还可以优化;
   - local_config_path: /tmp/jenkins/data/jenkins/workspace/TEST/xxx/production.js

  tasks:
  - name: register  datetime var(gray)
    command: date +%Y%m%d%H%M%S
    register: datetime

  - name: create a backup  directory if it does not exist(gray)
    file:
      # path: /home/backup/xxx/{{datetime.stdout}}
      # path: /home/backup/xxx/gray/{{datetime.stdout}}
      path: /home/backup/xxx/gray/{{datetime.stdout}}
      state: directory
      mode: '0755'

  - name: backup files(gray)
    command: tar -czf  /home/backup/xxx/gray/{{datetime.stdout}}/xxxx.tar.gz ./
    args:
      chdir: /var/www/html/xxx/gray/pc/

  - name: chmod dir chown videohy(gray)
    command: find {{gray_path}} -exec chown nginx:nginx  {} \;

  - name: Recursively remove directory(gray)
    file:
      path: "{{gray_path}}"
      state: absent

  - name: decompression to the target server(gray)
    unarchive:
      src: "{{update_file}}"
      dest: /var/www/html/xxx/gray/pc
      copy: yes
  
  - name: Copy from grayscale environment
    command: cp -af {{gray_path}} $(dirname {{online_path}})
    tags: online

  - name: Copy the configuration file to the target server
    copy:
      src: {{local_config_path}}
      dest: "{{online_path}}/static/js/production.js"
    tags: online
    
  - name: chmod file 0644
    command: find {{online_path}} -type f -exec chmod 0644 {} \;
    tags: online

  - name: chmod file 0755
    command: find {{online_path}} -type d -exec chmod 0755 {} \;
    tags: online

  - name: chmod dir chown nginx
    command: find {{online_path}} -exec chown nginx:nginx  {} \;
    tags: online

  - name: chmod file 0644
    command: find {{gray_path}} -type f -exec chmod 0644 {} \;

  - name: chmod file 0755
    command: find {{gray_path}} -type d -exec chmod 0755 {} \;

  - name: chmod dir chown nginx
    command: find {{gray_path}} -exec chown nginx:nginx  {} \;

本文作者:k-free

本文链接:https://www.cnblogs.com/k-free-bolg/p/17762691.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   k-free  阅读(123)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 新時代 (ウタ from ONE PIECE FILM RED) Ado
新時代 (ウタ from ONE PIECE FILM RED) - Ado
00:00 / 00:00
An audio error has occurred.

作词 : 中田ヤスタカ

作曲 : 中田ヤスタカ

新時代はこの未来だ

世界中全部 変えてしまえば

変えてしまえば

ジャマモノ やなもの なんて消して

ジャマモノ やなもの なんて消して

この世とメタモルフォーゼしようぜ

ミュージック

キミが起こす マジック

目を閉じれば未来が開いて

目を閉じれば未来が開いて

いつまでも終わりが来ないようにって

この歌を歌うよ

Do you wanna play? リアルゲーム ギリギリ

Do you wanna play? リアルゲーム ギリギリ

綱渡りみたいな旋律

認めない戻れない忘れたい

夢の中に居させて I wanna be free

見えるよ新時代が 世界の向こうへ

さあ行くよ NewWorld

新時代はこの未来だ

新時代はこの未来だ

世界中全部 変えてしまえば

変えてしまえば

果てしない音楽がもっと届くように

夢は見ないわ キミが話した

「ボクを信じて」

あれこれいらないものは消して

あれこれいらないものは消して

リアルをカラフルに越えようぜ

ミュージック

今始まる ライジング

目をつぶりみんなで逃げようよ

目をつぶりみんなで逃げようよ

今よりイイモノを見せてあげるよ

この歌を歌えば

Do you wanna play? リアルゲーム ギリギリ

Do you wanna play? リアルゲーム ギリギリ

綱渡りみたいな運命

認めない戻れない忘れたい

夢の中に居させて I wanna be free

見えるよ新時代が 世界の向こうへ

さあ行くよ NewWorld

新時代わ この未来を

新時代わ この未来を

世界中全部 変えてしまえば 変えてしまえば

果てしない音楽がもっと届くように

夢を見せるよ 夢を見せるよ

新時代だ

新時代だ

新時代だ