zoukankan      html  css  js  c++  java
  • .NET项目从CI到CD-Jenkins_Pipeline的应用

    一、罗里吧嗦

    最近迁移了服务器,顺道完善下服役了一两年的Jenkins服务,主要是把Slave搭建起来,还有等等。本文只是我对Jenkins Pipeline的一些自己的理解与应用,欢迎指出错误,欢迎交流高级应用

    二、运行环境

    Jenkins:

    1. master:阿里云Windows_2016_x64
    2. Slave1:京东云Windows_2008_r2_x64
    3. Slave2:阿里云Windows_2008_r2_x86

    版本管理器:自建的git服务器,使用gogs

    .NET项目:使用VS2017新建的一个web mvc项目与一个windows service项目,项目上传至git服务器

    一些辅助工具:

    1. 7-zip:作为压缩 解压
    2. ossutil:阿里云oss服务工具
    3. nuget:还原解决方案引用包
    4. MSBuild:编译项目

    三、开始

    首先新建.NET项目,新建一个web项目与windows service项目,步骤略

    其次,在自行安装Jenkins,步骤略


    新建Jenkins项目,类型选择Pipeline,命名为JenkinsPipelineProject

    整体流程如下

    start->检出代码->还原引用包->编译->打包->上传OSS->分发slave->发布web->发布Service->end

    各步骤:

    检出代码:使用内置的工具进行代码的检出,如我使用的是git

    还原引用包:使用nuget.exe对解决方案进行引用包还原,包源可选国内节点,国内节点下载速度框

    编译:此处进行了两次编译,一次编译web,一次编译Service

    打包:并行进行,对编译步骤得到的文件进行打包(使用7zip),存放于本地路径上,打包时,会删除相关配置文件,配置文件为手动更新

    上传OSS:对刚打包好的更新包进行上传,因两台服务器处于阿里云内网,所以采用阿里云的OSS,更新速度快

    分发Slave:根据配置的节点,进行更新web和service操作

    发布web:首先从OSS下载文件下来, 停止站点(非停止IIS),使用7zip进行解压文件,更新文件,更新完毕后启动站点,如有多台服务器需要更新,则并行执行,互不干扰

    发布Service:首先从OSS下载文件下来,停止对应的windows服务,卸载对应的windows服务,如若失败,则进行强制删除windows服务,之后使用7zip进行文件的解压更新,更新完毕后安装服务,并启动服务

    以下为具体的Pipeline代码

    注:

    1. 代码中所需的配置为我自己本身项目需要,如若更改,可根据自己项目进行定制
    2. 代码中一些敏感的配置已用xxxx代替
    3. 仅用于参考
    //编译服务器设置start
    def buildNodeSettings = [:]
    buildNodeSettings.node = '阿里云Windows_2008_r2_x86'//编译服务器节点设置
    buildNodeSettings.gitUrl = 'https://xxx/JenkinsPipelineProject.git'//git地址
    buildNodeSettings.gitBarnches = '*/master' //分支
    buildNodeSettings.slnFile = 'JenkinsPipelineProject.sln' //Nuget还原解决方案名
    
    buildNodeSettings.buildFileForWeb ='JenkinsPipelineProjectWeb\JenkinsPipelineProjectWeb.csproj' //msbulid编译文件名 web
    buildNodeSettings.msbuildArgForWeb = '/t:Rebuild /p:Configuration=Release;PublishProfile=FolderProfile;DeployOnBuild=true' //msbulid参数 web
    buildNodeSettings.publishOutputForWeb = '\JenkinsPipelineProjectWeb\bin\Release\PublishOutput' //编译后发布的路径 web
    buildNodeSettings.publishFileNameForWeb = env.JOB_NAME + '/Build-Web-' +env.BUILD_NUMBER + '.7z'  //文件名 
    buildNodeSettings.delFilesForWeb = ["Web.config","Web.Debug.config","Web.Release.config"] as String[]  //需要删除的文件
    
    buildNodeSettings.buildFileForService ='JenkinsPipelineProject.sln' //msbulid编译文件名 Service
    buildNodeSettings.msbuildArgForService = '/t:Rebuild /p:Configuration=Release' //msbulid参数 Service
    buildNodeSettings.publishOutputForService = '\JenkinsPipelineProjectWindowsService\bin\Release' //编译后发布的路径 Service
    buildNodeSettings.publishFileNameForService = env.JOB_NAME + '/Build-Service-' +env.BUILD_NUMBER + '.7z'  //文件名 
    buildNodeSettings.delFilesForService = ["*.config"] as String[]  //需要删除的文件
    
    buildNodeSettings.updateServerPath = 'D:\WebRoot\update\public_html\'//更新服务器存放包地址
    //编译服务器设置end
    
    def webNodeSetting = [:]
    webNodeSetting.node = '阿里云Windows_2008_r2_x86' //Web服务器节点
    webNodeSetting.downloadPath = 'C:\Jenkins\download\'//更新包下载地址
    webNodeSetting.publishPath = 'D:\WebRoot\JenkinsPipelinePorject\Web' //web服务器网站根目录
    webNodeSetting.webApplicationName = 'JenkinsPipelinePorject'//web站点名称
    
    def webNodeSetting2 = [:]
    webNodeSetting2.node = 'master' //Web服务器节点
    webNodeSetting2.downloadPath = 'C:\JenkinsDownload\'//更新包下载地址
    webNodeSetting2.publishPath = 'D:\WebRoot\JenkinsPipelinePorject\Web' //web服务器网站根目录
    webNodeSetting2.webApplicationName = 'JenkinsPipelinePorject'//web站点名称
    
    def webNodeSetting3 = [:]
    webNodeSetting3.node = '京东云Windows_2008_r2_x64' //Web服务器节点
    webNodeSetting3.downloadPath = 'C:\Jenkins\download\'//更新包下载地址
    webNodeSetting3.publishPath = 'C:\WebRoot\JenkinsPipelinePorject\Web' //web服务器网站根目录
    webNodeSetting3.webApplicationName = 'JenkinsPipelinePorject'//web站点名称
    
    def serviceNodeSetting = [:]
    serviceNodeSetting.node = '阿里云Windows_2008_r2_x86'
    serviceNodeSetting.downloadPath = 'C:\Jenkins\download\'//更新包下载地址
    serviceNodeSetting.publishPath = 'D:\WebRoot\JenkinsPipelinePorject\Service' //Service Windows Service存放路径
    serviceNodeSetting.serviceName = 'JenkinsPipelineProject'//服务名称
    serviceNodeSetting.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服务的文件名,相对publishPath的路径
    
    def serviceNodeSetting2 = [:]
    serviceNodeSetting2.node = 'master'
    serviceNodeSetting2.downloadPath = 'C:\Jenkins\download\'//更新包下载地址
    serviceNodeSetting2.publishPath = 'D:\WebRoot\JenkinsPipelinePorject\Service' //Service Windows Service存放路径
    serviceNodeSetting2.serviceName = 'JenkinsPipelineProject'//服务名称
    serviceNodeSetting2.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服务的文件名,相对publishPath的路径
    
    def serviceNodeSetting3 = [:]
    serviceNodeSetting3.node = '京东云Windows_2008_r2_x64'
    serviceNodeSetting3.downloadPath = 'C:\Jenkins\download\'//更新包下载地址
    serviceNodeSetting3.publishPath = 'C:\WebRoot\JenkinsPipelinePorject\Service' //Service Windows Service存放路径
    serviceNodeSetting3.serviceName = 'JenkinsPipelineProject'//服务名称
    serviceNodeSetting3.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服务的文件名,相对publishPath的路径
    
    node(buildNodeSettings.node) {
    
        def msbuild=tool name: 'MSBuildTool V14.0', type: 'msbuild' //编译工具名称与地址
        buildNodeSettings.publishOutputForWeb  = env.WORKSPACE + buildNodeSettings.publishOutputForWeb 
        buildNodeSettings.publishOutputForService  = env.WORKSPACE + buildNodeSettings.publishOutputForService 
        
        stage('Check Out')
        {
            echo '检出项目'
            checkout([$class: 'GitSCM', branches: [[name: buildNodeSettings.gitBarnches]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'xxxxxx', url: buildNodeSettings.gitUrl]]])
        }
        
        stage('Nuget Restore')
        {
            echo ' 还原nuget '
            echo '${env.nuget} restore "' + env.WORKSPACE + '/' + buildNodeSettings.slnFile + '" -ConfigFile "' + env.config + '" -NoCache'
            bat env.nuget + ' restore "' + env.WORKSPACE + '/' + buildNodeSettings.slnFile + '" -ConfigFile "' + env.config + '" -NoCache'
        }
            
        stage('Bulid')
        {
            echo ' 编译项目'
            echo 'Bulid Web'
            bat '"' + msbuild + '" ' + buildNodeSettings.msbuildArgForWeb + ' "' + env.WORKSPACE + '/' + buildNodeSettings.buildFileForWeb + '"'
            echo 'Bulid Service'
            bat '"' + msbuild + '" ' + buildNodeSettings.msbuildArgForService + ' "' + env.WORKSPACE + '/' + buildNodeSettings.buildFileForService + '"'
        }
    
        stage('Pack') {
            parallel PackWeb:{
                echo '删除相关配置文件'
                buildNodeSettings.delFilesForWeb.each{
                    echo '删除文件:' + it
                    def filepath ='"' + buildNodeSettings.publishOutputForWeb.replace("/","\") + '\' + it + '"'
                    bat 'if exist '+ filepath +' del ' + filepath
                }
                echo ' 发布到更新系统'
                bat 'if not exist "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '" md "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '"'
                bat '"' + env.zip + '"'+ ' a -r "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForWeb + '" "' + buildNodeSettings.publishOutputForWeb + '\*"'
                echo '压缩完成'
                echo '上传oss'
                bat env.oss + ' -c ' + env.ossconfig + ' cp "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForWeb + '" "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb +'"'
            },
            PackService:{
                echo '删除相关配置文件'
                buildNodeSettings.delFilesForService.each{
                    echo '删除文件:' + it
                    def filepath ='"' + buildNodeSettings.publishOutputForService.replace("/","\") + '\' + it + '"'
                    bat 'if exist '+ filepath +' del ' + filepath
                }
                echo ' 发布到更新系统'
                 bat 'if not exist "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '" md "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '"'
                bat '"' + env.zip + '"'+ ' a -r "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForService + '" "' + buildNodeSettings.publishOutputForService + '\*"'
                echo '压缩完成'
                echo '上传oss'
                bat env.oss + ' -c ' + env.ossconfig + ' cp "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForService + '" "oss://xxxx/' + buildNodeSettings.publishFileNameForService +'"'
            }
        }
    
        stage('Clear')
        {
            echo '清理工作目录'
            deleteDir()
        }
    }
    
    stage('Publish Web')
    {
        parallel publishWeb1:{
            node(webNodeSetting.node)
            {
                echo '发布web'
                echo '更新文件'
                echo '更新文件下载地址为:http://xxxx/' + buildNodeSettings.publishFileNameForWeb
                echo '下载文件'
                bat env.oss + ' -c ' + env.ossconfig + ' cp  "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting.downloadPath
                echo '文件下载完成'
                echo '停止站点'
                bat 'C:\Windows\System32\inetsrv\appcmd.exe stop site "' + webNodeSetting.webApplicationName + '"'
                bat '"' + env.zip + '" x "'+ webNodeSetting.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting.publishPath + '"'
                echo '启动站点'
                bat 'C:\Windows\System32\inetsrv\appcmd.exe start site "' + webNodeSetting.webApplicationName+ '"'
            }
        },
        publishWeb2:{
            node(webNodeSetting2.node)
            {
                echo '发布web'
                echo '更新文件'
                echo '更新文件下载地址为:http://xxxx/' + buildNodeSettings.publishFileNameForWeb
                echo '下载文件'
                bat env.oss + ' -c ' + env.ossconfig + ' cp  "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting2.downloadPath
                echo '文件下载完成'
                echo '停止站点'
                bat 'C:\Windows\System32\inetsrv\appcmd.exe stop site "' + webNodeSetting2.webApplicationName + '"'
                bat '"' + env.zip + '" x "'+ webNodeSetting2.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting2.publishPath + '"'
                echo '启动站点'
                bat 'C:\Windows\System32\inetsrv\appcmd.exe start site "' + webNodeSetting2.webApplicationName+ '"'
            }
        },
        publishWeb3:{
            node(webNodeSetting3.node)
            {
                withEnv(['oss=C:\Tools\oss\ossutil.exe', 'ossconfig=C:\Tools\oss\config']) {//需要手动设置变量
                    echo '发布web'
                    echo '更新文件'
                    echo '更新文件下载地址为:http://xxxx/' + buildNodeSettings.publishFileNameForWeb
                    echo '下载文件'
                    bat env.oss + ' -c ' + env.ossconfig + ' cp  "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting3.downloadPath
                    echo '文件下载完成'
                    echo '停止站点'
                    bat 'C:\Windows\System32\inetsrv\appcmd.exe stop site "' + webNodeSetting3.webApplicationName + '"'
                    bat '"' + env.zip + '" x "'+ webNodeSetting3.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting3.publishPath + '"'
                    echo '启动站点'
                    bat 'C:\Windows\System32\inetsrv\appcmd.exe start site "' + webNodeSetting3.webApplicationName+ '"'
                }
            }
        }
    }
    
    stage('Publish Service')
    {
        parallel publishService1:
        {
            node(serviceNodeSetting.node){
                
                //发布windows service
                echo '发布Service'
                echo '下载文件'
                bat env.oss + ' -c ' + env.ossconfig + ' cp  "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting.downloadPath
                echo '卸载Windows Services'
                try{
                    bat 'net stop ' + serviceNodeSetting.serviceName
                    bat env.InstallUtil + ' -u ' + serviceNodeSetting.serviceName
                }catch(ex)
                {
                    echo '卸载失败:' + ex
                    try{
                        bat 'sc delete ' + serviceNodeSetting.serviceName
                    }catch(ex2)
                    {
                        echo '强制删除失败:' +ex2
                    }
                }
                echo '解压文件'
                bat '"' + env.zip + '" x "'+ serviceNodeSetting.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting.publishPath + '"'
                echo '服务安装'
                bat env.InstallUtil + ' ' + serviceNodeSetting.publishPath + '\' + serviceNodeSetting.serviceFileName + ' /name='+ serviceNodeSetting.serviceName + ' /display=' + serviceNodeSetting.serviceName + ' /desc=' + serviceNodeSetting.serviceName
                echo '启动服务'
                bat 'net start ' + serviceNodeSetting.serviceName
                
            }
        },
        publishService2:
        {
            node(serviceNodeSetting2.node){
                
                //发布windows service
                echo '发布Service'
                echo '下载文件'
                bat env.oss + ' -c ' + env.ossconfig + ' cp  "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting2.downloadPath
                echo '卸载Windows Services'
                try{
                    bat 'net stop ' + serviceNodeSetting2.serviceName
                    bat env.InstallUtil + ' -u ' + serviceNodeSetting2.serviceName
                }catch(ex)
                {
                    echo '卸载失败:' + ex
                    try{
                        bat 'sc delete ' + serviceNodeSetting2.serviceName
                    }catch(ex2)
                    {
                        echo '强制删除失败:' +ex2
                    }
                }
                echo '解压文件'
                bat '"' + env.zip + '" x "'+ serviceNodeSetting2.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting2.publishPath + '"'
                echo '服务安装'
                bat env.InstallUtil + ' ' + serviceNodeSetting2.publishPath + '\' + serviceNodeSetting2.serviceFileName + ' /name='+ serviceNodeSetting2.serviceName + ' /display=' + serviceNodeSetting2.serviceName + ' /desc=' + serviceNodeSetting2.serviceName
                echo '启动服务'
                bat 'net start ' + serviceNodeSetting2.serviceName
                
            }
        },
        publishService3:
        {
            node(serviceNodeSetting3.node){
                withEnv(['oss=C:\Tools\oss\ossutil.exe', 'ossconfig=C:\Tools\oss\config']) {//需要手动设置变量
                    //发布windows service
                    echo '发布Service'
                    echo '下载文件'
                    bat env.oss + ' -c ' + env.ossconfig + ' cp  "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting3.downloadPath
                    echo '卸载Windows Services'
                    try{
                        bat 'net stop ' + serviceNodeSetting3.serviceName
                        bat env.InstallUtil + ' -u ' + serviceNodeSetting3.serviceName
                    }catch(ex)
                    {
                        echo '卸载失败:' + ex
                        try{
                            bat 'sc delete ' + serviceNodeSetting3.serviceName
                        }catch(ex2)
                        {
                            echo '强制删除失败:' +ex2
                        }
                    }
                    echo '解压文件'
                    bat '"' + env.zip + '" x "'+ serviceNodeSetting3.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting3.publishPath + '"'
                    echo '服务安装'
                    bat env.InstallUtil + ' ' + serviceNodeSetting3.publishPath + '\' + serviceNodeSetting3.serviceFileName + ' /name='+ serviceNodeSetting3.serviceName + ' /display=' + serviceNodeSetting3.serviceName + ' /desc=' + serviceNodeSetting3.serviceName
                    echo '启动服务'
                    bat 'net start ' + serviceNodeSetting3.serviceName
                }
            }
        }
    }

    以上代码对三台服务器上的web和service进行了更新操作,两台阿里云内网,一台京东云

    代码说明:

    node:节点,Slave,表示在哪个节点中运行
    stage:阶段,表示当前阶段,可定义阶段名称
    checkout:代码检出
    echo:输出信息
    bat:执行cmd命令,linux下命令为sh
    env:环境变量,有系统定义变量和自定义变量两部分
    parallel:表示并行执行步骤
    更多详细解释请查看官方文档https://jenkins.io/doc/book/pipeline/

    四、看看效果

    我们开始构建刚才新建的项目

    从gif可以看出,整个流程只耗费了一分钟不到,我们去看看这三台服务器

    三台服务器的文件都已更新,并且服务已经启动,证明我们的pipeline代码是可行的

    五、补充和改进

    • 这篇文章的代码量过大,语言组织能力有待改进
    • Slave节点的环境变量不能正确读取到,目前只能使用withEnv进行更改环境变量,具体情况publishService3
    • 项目的耦合性太高了,目前编译、打包、发布都在同一个项目中,需要进行项目的拆分
    • 没有加重试机制,一旦某一阶段失败,只能重新运行,有待改进
    • 失败邮件通知,这个目前没有加入

    如若有写的不好的地方,请指出

    如若有更好的方案,欢迎一起交流

    如若有不懂,欢迎咨询,我会告诉你我知道的

    本文已同步个人博客,欢迎转载

  • 相关阅读:
    大型项目生产环境日志查询
    泛型接口和泛型方法
    使用360浏览器登录B站的时候,不显示搜索框
    构建之法第二周学习体验
    构建之法首周阅读体会
    C语言实例解析精粹学习笔记——26
    C语言实例解析精粹学习笔记——29
    C语言实例解析精粹学习笔记——28
    C语言实例解析精粹学习笔记——18
    C语言实例解析精粹学习笔记——30
  • 原文地址:https://www.cnblogs.com/ofnhkb1/p/7801220.html
Copyright © 2011-2022 走看看