• 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. 在项目中创建目录结构,如下所示;
    1. shared_library/
    2. ├── README.md
    3. ├── resources
    4. │ └── org
    5. │ └── devops
    6. ├── src
    7. │ └── org
    8. │ └── devops
    9. │ ├── build_deploy.groovy
    10. │ ├── checkout_code.groovy
    11. │ └── email_notification.groovy
    12. └── vars
    13. 7 directories, 4 files

    2.1 共享库介绍

    • build_deploy.groovy
    1. package org.devops
    2. // 定义编译函数,传入两个参数
    3. // Env: 要发布的环境
    4. // Buildcommand: 编译时的命令,由于多个项目可能编译命令不一样,所以这里以接收参数的方式;
    5. def Build(Env,Buildcommand){
    6. if (Env == "gray") {
    7. // 需求说明了,如果是灰度环境的话就要拉取源码进行编译,所以这里执行编译命令,并打印当前发布环境;
    8. sh Buildcommand
    9. println("gray环境")
    10. // 如果是生产的话则不进行编译,直接从发布后的gray环境中复制即可;具体实现在playbook中;
    11. } else if(Env == "online"){
    12. println("生产环境不编译")
    13. // 如果是全部发布的话则跟灰度一个逻辑,不过就是打印的结果不一样,如果打印无所谓的话也可以写到上面条件变成or;
    14. } else if(Env == "all"){
    15. sh Buildcommand
    16. println("全环境发布")
    17. }
    18. }
    19. // 定义获取配置文件方法,传四个参数
    20. // Env: 要发布的环境;
    21. // project: gitlab对应的项目名称;
    22. // filename: 配置文件的名称;
    23. // path: 配置文件要放到哪个位置(主要是灰度环境用)
    24. def Get_Config(Env,project,filename,path){
    25. if (Env == "gray") {
    26. // 如果环境是灰度的话;先判断编译目录下是否有"gray_env"目录,有就删除;然后clone项目地址到本地的gray_env目录下,并且将项目中的配置文件挪到编译后的目录中;
    27. sh "`[ -d ./gray_env ] && rm -rf ./gray_env/ || :` && git clone ssh://git@xxxxx/xxx/${project} gray_env && mv -f gray_env/${filename} ./${path}"
    28. // 如果是生产的话跟上面一样,不过目录名是online_env,将项目中的配置文件挪到编译目录;
    29. }else if(Env == "online"){
    30. sh "`[ -d ./online_env ] && rm -rf ./online_env/ || :` && git clone ssh://git@xxxx/xxxxx/${project} online_env && mv -f online_env/${filename} ./"
    31. // 如果是全发布的话则两步都走;
    32. }else if(Env == "all"){
    33. sh "`[ -d ./gray_env ] && rm -rf ./gray_env/ || :` && git clone ssh://git@xxxx/xxx/${project} gray_env && mv -f gray_env/${filename} ./${path}"
    34. sh "`[ -d ./online_env ] && rm -rf ./online_env/ || :` && git clone ssh://git@xxxx/xxxx/${project} online_env && mv -f online_env/${filename} ./"
    35. }
    36. }
    37. // 定义压缩编译后程序并发到目标服务器上
    38. def compress_copy(Env){
    39. if (Env == "gray" || Env == "all") {
    40. // 将目录打包名为"jenkins项目的名字".tar.gz文件,并忽略调本地的.svn目录
    41. sh "tar czf ${JOB_BASE_NAME}.tar.gz --exclude=.svn ./dist"
    42. // 将文件传到发布机器的/tmp/目录下
    43. sh "scp -P50022 ${JOB_BASE_NAME}.tar.gz sysvideo@$ansible的机器地址:/tmp/"
    44. }
    45. }
    46. //定义发布方法,传四个参数
    47. // Env: 要发布的环境;
    48. // file_path: playbook的路径,相对于"/etc/ansible/jenkins"目录的相对路径;
    49. // config_name: 对应的配置文件名称
    50. def Deploy(Env,file_path,config_name){
    51. if (Env == "gray") {
    52. // 如果是灰度环境的话则跳过playbook中tags为online的步骤进行发布
    53. println("灰度环境跟以前线上环境发布一样")
    54. // 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}"
    55. }else if(Env == "online") {
    56. // 如果是online的话则需要先将配置文件拉到本机,也就是ansible这台机器上;随后调用下面的playbook中online的tags进行发布
    57. sh "sudo ansible-playbook -e config_name='${config_name}' -e job_name='${JOB_BASE_NAME}' /etc/ansible/jenkins/global/pull_config_from_jenkins.yml"
    58. println("发布生产环境,先把配置文件从jenkins拉到本地")
    59. // sh "ansible-playbook --tags=online /etc/ansible/jenkins/global/${file_path}"
    60. }else if(Env == "all") {
    61. sh "sudo ansible-playbook -e config_name='${config_name}' -e job_name='${JOB_BASE_NAME}' /etc/ansible/jenkins/global/pull_config_from_jenkins.yml"
    62. println("拉完配置文件之后,执行发布命令,默认会都跑一遍")
    63. // sh "cd /etc/ansible/jenkins/$(dirname ${file_path}) && sudo ansible-playbook -e update_file=/tmp/${JOB_BASE_NAME}.tar.gz ${file_path}"
    64. }
    65. }
    • checkout_code.groovy
    1. package org.devops
    2. // 定义获取代码的通用方法,接收一个参数
    3. // address: 代码所在的svn地址,仅支持SVN
    4. def CheckOut_Code(address) {
    5. checkout (
    6. changelog: false,
    7. poll: false,
    8. scm: [
    9. $class: 'SubversionSCM',
    10. additionalCredentials: [],
    11. excludedCommitMessages: '',
    12. excludedRegions: '',
    13. excludedRevprop: '',
    14. excludedUsers: '',
    15. filterChangelog: false,
    16. ignoreDirPropChanges: false,
    17. includedRegions: '',
    18. locations: [
    19. [
    20. cancelProcessOnExternalsFail: true,
    21. credentialsId: 'svn_pass',
    22. depthOption: 'infinity',
    23. ignoreExternalsOption: true,
    24. local: '.',
    25. remote: address]
    26. ],
    27. quietOperation: true,
    28. workspaceUpdater: [
    29. $class: 'UpdateUpdater']
    30. ]
    31. )
    32. }
    • email_notification.groovy
    1. package org.devops
    2. // 定义通用发送邮件方法,接收一个参数;
    3. // EmailUser: 收件人,默认为xxxx@netxx.com;
    4. def send_mail(EmailUser = 'xxxx@netxx.com') {
    5. mail (
    6. subject: "Status of pipeline : ${currentBuild.fullDisplayName}",
    7. body: "${env.BUILD_URL} has result ${currentBuild.result}",
    8. to: EmailUser,
    9. from: "jenkins@jenkins.com"
    10. )
    11. println(EmailUser)
    12. }

    4. pipeline编写

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

    1. // 引用共享库
    2. @Library("shared_library") _
    3. import org.devops.email_notification
    4. def email_notification = new org.devops.email_notification()
    5. def code = new org.devops.checkout_code()
    6. def build_deploy = new org.devops.build_deploy()
    7. pipeline {
    8. agent none
    9. tools {
    10. nodejs "nodejs 14.18.2"
    11. }
    12. parameters {
    13. choice(
    14. choices: "gray\nonline\nall",
    15. description: "选择发布到哪个环境",
    16. name: "environment"
    17. )
    18. string(
    19. name: "Code_Address",
    20. defaultValue: "None",
    21. description: "定义代码的SVN路径,默认为None"
    22. )
    23. string(
    24. name: "Config_Name",
    25. defaultValue: "None",
    26. description: "前端文件的文件名,如果是gray的可以不用写;"
    27. )
    28. }
    29. stages {
    30. stage("checkout code") {
    31. agent {
    32. label "master"
    33. }
    34. steps {
    35. script {
    36. /*
    37. 1. 拉取代码,从SVN地址获取代码的位置;这里只考虑了SVN的情况,未考虑Git;
    38. 2. 共享库位置在git,详情咨询kfreesre@163.com;
    39. */
    40. code.CheckOut_Code("${Code_Address}")
    41. }
    42. }
    43. }
    44. stage("build and Get Config") {
    45. agent {
    46. label "master"
    47. }
    48. steps {
    49. script {
    50. /*
    51. 1. Build将源码进行编译;
    52. 参数解释:
    53. 1. environment参数可固定;
    54. 2. npm config set ...; 代表编译命令,根据实际情况修改;
    55. 2. Get_Config从Git获取项目相关配置文件,environment参数可固定;
    56. 参数解释:
    57. 1. environment参数可固定;
    58. 2. xxxx: 配置文件所在的git仓库名称;同一项目的灰度与生产名称一致,传入一个就行,可去git确认;
    59. 3. production.js: 配置文件的名称;
    60. 4. dist/static/js/: 当源码编译后,会在workspace中生成一个dist的目录,将3中的"production.js"拷贝到编译后的目录中;git上项目的描述中说明了具体位置;
    61. */
    62. build_deploy.Build("${environment}","npm config set registry 私库地址 && npm install && npm run build")
    63. build_deploy.Get_Config("${environment}","xxxx","production.js","dist/static/js/")
    64. }
    65. }
    66. }
    67. stage("compress and copy") {
    68. agent {
    69. label "master"
    70. }
    71. steps {
    72. script {
    73. /*
    74. 1. 将编译后的dist目录打包为压缩包,命名为当前jenkins项目的名称;
    75. 2. 随后将打包后的tar.gz文件发送到云视ansible的/tmp目录下;
    76. */
    77. build_deploy.compress_copy("${environment}")
    78. }
    79. }
    80. }
    81. stage("Deploy") {
    82. agent {
    83. label "Deploy"
    84. }
    85. steps {
    86. script {
    87. /*
    88. 参数解释:
    89. 1. environment可固定,不用修改会自动获取在点击构建时选择的环境;
    90. 2. xxxx/ngin_update_all.yml是对应的anible yaml文件,这里填写相对路径,相对的是/etc/ansible/jenkins;
    91. */
    92. build_deploy.Deploy("${environment}","xxxx/update_all.yml","${Config_Name}")
    93. }
    94. }
    95. }
    96. }
    97. post {
    98. always {
    99. script {
    100. /*
    101. 每次都发送邮件,默认发给"xxxx@xxx.com",如果要修改在send_mail()中传参即可,类似 email_notification.send_mail("xxxx.xxx@net.com"),如果有多个收件人可以逗号为分隔符
    102. */
    103. email_notification.send_mail("xxxx@netxxx.com,xxxx.xx@netxxx.com")
    104. }
    105. }
    106. }
    107. }

    5. playbook编写

    1. 编写online环境需要拉取配置文件的playbook
    1. - hosts: jenkins
    2. become: yes
    3. become_method: sudo
    4. become_user: root
    5. vars:
    6. - config_name: None
    7. - job_name: None
    8. # 这里写上jenkins工作目录,我这里TEST是jenkins-UI上创建的前端项目所在文件夹,所以固定;
    9. - config_path: /data/jenkins/workspace/TEST/{{job_name}}
    10. - local_config_path: None
    11. tasks:
    12. - name: Pull the configuration file
    13. fetch:
    14. src: "{{ config_path }}/{{ config_name }}"
    15. dest: /tmp/
    16. register: dest_path
    1. 编写online及gray环境发布的playbook(每个项目所在的目录不一样,所以发布的项目对应的playbook基于这个修改即可)
    1. ---
    2. - hosts:
    3. - xxxx
    4. - xxxx
    5. become: yes
    6. become_method: sudo
    7. become_user: root
    8. vars:
    9. # - update_path: /var/www/html/xxxx/pc/dist
    10. - gray_path: /var/www/html/gray/xxxx/pc/dist
    11. - online_path: /var/www/html/xxxx/pc/dist
    12. - update_file: default
    13. # 这里就直接写死了,因为每个项目都对应一个playbook,不过还可以优化;
    14. - local_config_path: /tmp/jenkins/data/jenkins/workspace/TEST/xxx/production.js
    15. tasks:
    16. - name: register datetime var(gray)
    17. command: date +%Y%m%d%H%M%S
    18. register: datetime
    19. - name: create a backup directory if it does not exist(gray)
    20. file:
    21. # path: /home/backup/xxx/{{datetime.stdout}}
    22. # path: /home/backup/xxx/gray/{{datetime.stdout}}
    23. path: /home/backup/xxx/gray/{{datetime.stdout}}
    24. state: directory
    25. mode: '0755'
    26. - name: backup files(gray)
    27. command: tar -czf /home/backup/xxx/gray/{{datetime.stdout}}/xxxx.tar.gz ./
    28. args:
    29. chdir: /var/www/html/xxx/gray/pc/
    30. - name: chmod dir chown videohy(gray)
    31. command: find {{gray_path}} -exec chown nginx:nginx {} \;
    32. - name: Recursively remove directory(gray)
    33. file:
    34. path: "{{gray_path}}"
    35. state: absent
    36. - name: decompression to the target server(gray)
    37. unarchive:
    38. src: "{{update_file}}"
    39. dest: /var/www/html/xxx/gray/pc
    40. copy: yes
    41. - name: Copy from grayscale environment
    42. command: cp -af {{gray_path}} $(dirname {{online_path}})
    43. tags: online
    44. - name: Copy the configuration file to the target server
    45. copy:
    46. src: {{local_config_path}}
    47. dest: "{{online_path}}/static/js/production.js"
    48. tags: online
    49. - name: chmod file 0644
    50. command: find {{online_path}} -type f -exec chmod 0644 {} \;
    51. tags: online
    52. - name: chmod file 0755
    53. command: find {{online_path}} -type d -exec chmod 0755 {} \;
    54. tags: online
    55. - name: chmod dir chown nginx
    56. command: find {{online_path}} -exec chown nginx:nginx {} \;
    57. tags: online
    58. - name: chmod file 0644
    59. command: find {{gray_path}} -type f -exec chmod 0644 {} \;
    60. - name: chmod file 0755
    61. command: find {{gray_path}} -type d -exec chmod 0755 {} \;
    62. - name: chmod dir chown nginx
    63. command: find {{gray_path}} -exec chown nginx:nginx {} \;
  • 相关阅读:
    操作系统——快速复习笔记02
    Re:从零开始的C++世界——类和对象(中)
    1,2-二苯基-1,2-二(4-羧基苯)乙烯 ;CAS: 1609575-40-7
    Javascript核心技术的基础语法
    Parallels Desktop 20 for Mac 正式发布,更新了哪些新功能(附下载链接)!
    【文件传输】实现下载
    QCommandLineOption、QCommandLineParser
    水球展示——微信小程序
    【物理应用】基于matlab GUI气象参数计算综合指标和IAQI【含Matlab源码 2116期】
    免费分享一个springboot+vue学生选课管理系统,挺漂亮的
  • 原文地址:https://blog.csdn.net/kaikai0720/article/details/133859126