Jenkins实现DevOps流水线

242 阅读22分钟

引言

DevOps 是一套实践方法,在保证高质量的前提下缩短系统变更从提交到部署至生产环境的时间,其中持续集成和持续交付是 DevOps 里面非常重要的一环。本文讲述了达到自动化持续交付需要做的准备工作,流水线构建方法和最佳实践。

关于持续交付

持续交付是一组能够帮助软件开发团队极大的提高其软件交付的速度和质量的模式和最佳实践组成。

不同于低频率发布相对较大的版本,实施持续交付的团队希望比通常更频繁地将更小批量的变更投入生产, 例如每周,每天或一天之内就能够发布多个版本。

这种软件交付方式可以带来许多的好处,就像市场领导者 Facebook,LinkedIn 和 Twitter 那样,他们频繁地,以迭代方式发布软件,并取得了巨大的成功。 然而,要达到这种结果,需要对您开发和交付的方式进行一些潜在的重大变革。

持续交付的核心价值

软件的核心价值是为软件的使用者带来收益,在过去我们经常听到开发人员说这个功能已经开发完成了。 但是在持续交付中我们认为之后将特性真正的发布到用户手上才以为则完成。

终极目标

持续交付将在以下方面为您提供帮助:

  • 尽可能快地交付软件,尽可能早地将有价值的新功能运用于生产

  • 提高软件质量,系统正常运行时间和稳定性

  • 降低发布风险,避免同时在测试和生产环境部署失败

  • 减少浪费,提高开发和交付过程的效率

  • 使您的软件始终处于生产就绪状态,以便您可以随时部署

Pipeline实现持续交付

TODO 待写入

创建一个可重复且可靠的自动化过程

为了达到目的,您首先需要有以下基础:

  • 自动化测试等开发实践

  • 软件架构和组件设计,可帮助做更频繁的发布,而不影响用户,包括功能标志

  • 工具如源代码管理,持续集成,配置管理和应用发布自动化软件

  • 自动化和脚本化,使您能够以有限的人为干预重复构建,打包,测试,部署和监控软件

  • 组织,文化和业务流程的变化,以支持持续交付

听到持续交付这个词,有些人的第一个担忧就是这是否意味着软件质量标准将会下滑,否则团队需要走捷径才能实现软件的频繁发布。

事实恰恰相反,为了支持持续交付而采取的措施和体系几乎肯定会提高软件发布的质量,并且在软件版本出错时将给予您额外的安全防护。

您的软件仍然会经历与现在相同的严格测试阶段,可能包括手动质量检查测试阶段。持续交付只是让您的软件以最严格和最有效的方式在您设计的流程中流转,从开发到最后的生产。

持续交付的关键构建模块:自动化!

尽管在持续交付流程中采取手动步骤是非常有效和现实的,但自动化是加快交付步伐和缩短周期时间的关键。

毕竟,即使拥有再丰富资源的团队,手工构建,打包,编译,测试和部署软件也是不可行的,尤其是在软件很大或者复杂的情况下。

因此,最重要的目标应该是使开发者和生产环境之间的大部分路径自动化。 以下是您应该专注于自动化工作的一些主要领域。

自动化构建和打包

需要实现自动化的第一件事就是将开发人员的源代码转换为部署就绪制品的这个过程。虽然大多数软件开发人员使用诸如 Make,Ant,Maven,NuGet,npm 等工具来管理其构建和打包,但是许多团队在制作好准备发布的制品之前仍需要执行一些手动步骤。

这些步骤是实现持续交付的重大障碍的代表。 例如,如果每三个月发布一次,手动构建与安装就显得不那么繁杂。但是,如果您希望每天或每周发布多次,那么这个任务可靠的自动化会更合适。

目标:实现单个脚本或命令,使您能够将版本控制的源代码转换为单个可部署的制品。

自动化持续集成

持续集成是持续交付的基本组成部分。它涉及整合多个开发人员的代码,并不断编译和测试集成的代码库,以便尽可能早地识别错误。

理想情况下,此过程将利用自动化构建,从而使您的持续集成服务器不断地发布包含开发团队集成工作的部署制品,每个构建的结果都是可行的发布候选。

通常,您将建立一个持续集成服务器或相关云服务(如 Jenkins,TeamCity 或 Team Foundation Server),每天可能会执行多次集成,很可能在每次提交时触发。

第三方持续集成服务,如 DaoCloud Services,CloudBees DEV@cloud,Travis CI 或 CircleCI 可以帮助您加快您的持续集成进度。 通过外包您的持续整合平台,您可以自由地专注于持续交付的目标,而不是管理工具和基础架构。

目标:
1.实现持续集成过程就是持续输出一组可用于部署的制品
2.评估基于云的持续集成产品,以加快您的持续交付进程
3.通过发布跟踪软件(如Jira)的集成,整合对每个构建所发生变化的详细审计跟踪

您的持续集成工具可能对您的持续交付工作至关重要。 例如,它可以超越构建并进入测试和部署。 因此,持续集成是您持续交付战略的关键要素。

自动化测试

虽然持续交付可以(并且经常)包括由质量保证团队执行的手动测试阶段或最终用户验收测试,但是自动化测试几乎肯定将是您加快交付周期并提高质量的关键功能。

通常,您的持续集成服务器将负责执行大多数的自动化测试,以验证每个开发人员提交的代码。

然而,当系统部署到测试环境中时,某些自动化测试可能需要被执行,因此您还应该尽可能多的实现自动化测试。您的自动化测试应该是详尽的,能够覆盖测试应用程序的多个方面:

单元测试

底层函数和类在不同输入条件下按照预期工作

集成测试

集成模块与消息队列及数据库等基础设施协同工作

验收测试

通过用户界面测试关键用户操作流程,并将您的应用程序作为一个完整的黑盒子

负载测试

测试您的应用程序在模拟的真实用户负载下是否运行良好

性能测试

该应用程序在实际负载情况下满足性能要求和响应时间要求

模拟测试

您的应用程序在设备仿真环境中工作。这在移动端尤其重要,您需要在各种模拟的移动设备上测试软件

冒烟测试

验证新部署环境的状态和完整性

质量测试

应用代码是高质量的 — 通过静态分析,代码风格指南,代码覆盖度等技术来识别

理想情况下,这些测试可以分布在部署流水线中,随着流水线中的测试越来越详细和价值越来越高,在生产环境中,这些发布候选制品看起来越来越可靠。其目标应该是尽早确定有问题的构建,以避免返工,尽快缩短周期时间和获得反馈。

目标:
1.让您的测试尽可能多的实现自动化
2.提供针对代码部件和部署系统的多级抽象的良好测试覆盖
3.在您的部署流水线中分发不同类别的测试,模拟日后的生产环境并进行更加详细的测试,同时避免人力返工

在微服务风格的环境中,跨部署组件的集成和协同测试越来越重要。在这样的环境中,自动化部署所有必需的应用程序的功能成为高优先级的任务。

自动化测试是您发布高质量软件的主要防线,投资这些测试可能是昂贵的,但这一系列的自动化测试将在应用的整个生命周期内提供持续帮助。

自动化部署

软件团队通常需要将发布后续推送到不同部署环境进行上述讨论的不同类别的测试。

例如,常见的情况是将软件部署到测试环境进行人为的质量检查测试,然后部署到性能测试环境,进行自动化负载测试。 如果构建通过该测试阶段,则应用程序可能稍后部署到用于 UAT 或 beta 测试的独立环境中。

理想情况下,将任意发布候选制品以及与之通信的其他系统可靠地部署到任意环境中的这个过程应尽可能实现自动化。

如果您希望按照计划的速度持续交付,那可能需要每天或每周多次执行,因此它的工作速度和可靠性至关重要。

用自动化方式在环境之间移动软件是作为继续交付的团队的主要特性之一,因此这也是继续交付的关键重点。

目标:
1.能够简单的在任意环境中部署发布某任意特定版本
2.使用冒烟测试确保部署系统的可用性
3.加强部署过程,使其永远不会让环境处于断开或部分部署状态
4.将自助服务功能纳入此过程,因此质量保证人员或业务用户可以选择软件版本,并方便部署。在较大的组织中,此过程应5.包含业务规则,使特定用户具有特定环境的部署权限
6.评估应用的版本自动化工具,以加快持续部署能力

受管理的基础架构和云服务

在持续的交付环境中,可能希望具有更多灵活性和敏捷性来应对环境的创建和拆除,应对项目不断变化的需求。

如果要启动新的环境并添加到部署流水线中,而且该过程需要花费几个月的时间去购买硬件,配置操作系统,配置中间件并将其正确部署,因此敏捷性受到了严重限制,您的交付能力同时也受到了限制。

利用虚拟化和基于云的产品可以帮助您。 考虑像 阿里云,腾讯云,Amazon EC2,Microsoft Azure 或 Google Cloud Platform 这样的云端主机,您可以根据项目的要求灵活地开发新的环境和新的基础架构。

云也可以为生产应用程序做出最佳选择,使您在开发和生产环境中实现高度的一致性。

目标:
1.为持续交付流程提供更多灵活性,以便您可以按需伸缩您的流水线
2.在云中实施持续的交付基础设施,能够快速推出新环境,并在需求减少的情况下暂停或拆除这些环境

基础架构即代码

当配置方面不一致时候,例如当开发环境与测试不同,或当测试环境与生产不一致时,会发生非常常见的一系列生产事件,错误最终导致返工。

配置管理工具(如 Puppet,Chef,Ansible 或 Salt)和环境建模工具(如 Vagrant 或 Terraform)可帮助您通过将基础架构和平台提供版本控制代码来避免这种情况,然后将环境自动构建成一致和可重复的状态。

结合云和外包基础设施,这种鸡尾酒【混合模式】可以让您轻松部署准确配置的环境,从而为您的交付速度带来真正的提升。

Vagrant 和 Terraform 也可以为开发人员提供非常一致和可重复的开发环境,可以在自己的机器上进行虚拟化和运行。 容器是实现此目的的另一个常用选项。

这些工具都非常重要,因为环境的一致性是允许软件以一致和可靠的方式流过流水线的巨大推动力。

目标:
1.实施配置管理化,更加全面地控制构建环境,特别是与云结合使用
2.Vagrant,Terraform和容器框架,以此为开发者提供非常一致的本地开发环境

容器技术

另一种方式,容器在持续交付准备中的多个时刻都可能会出现:无论是生产环境的首次运行,或者创建可重复本地设置的更轻量级手段,还是简单的跟踪技术的未来潜能。

如果您决定尝试将应用程序容器化,请确保您已经了解了业务流程框架,如 Docker Swarm,Mesos 或 Kubernetes,这将帮助您将相关容器组定义和控制为单个版本化实体。

如果您计划在容器上运行生产环境,请确保您选择的业务流程框架也可用于本地开发。否则,会存在容器在开发机器上 “链接” 的方式与生产环境中不一致的风险。

自动的生产部署

虽然大多数软件团队的构建和测试都具有一定程度的自动化,但是在生产服务器上部署的实际行为仍然是典型软件团队最为手动的过程之一。

例如,团队可能会将多个二进制文件推送到多个服务器上,一些手动执行的数据库升级脚本,然后是一些手动安装步骤来将它们连接在一起。他们通常也会执行系统启动的手动步骤和冒烟测试。

由于这种复杂性,发布经常发生在业务工作时间之外。事实上,一些不幸的软件团队必须在星期天早上凌晨3点进行升级和定期维护,以免造成影响!

要实现持续交付,您需要解决这一痛苦,悠闲的编写脚本并自动从您的生产发布过程中代替手动执行步骤,以便可以重复和一致地运行。理想情况下,您需要做到能够在机器还在使用的业务时间内完成以上过程。这可能会对系统的体系结构产生重大影响。

为了实现使用中的系统能够每天生产部署多次,要确保该过程也经过了测试和加固,以免由于部署失败而使生产应用程序处于断开状态。

目标:
1.完全自动化生产部署过程,使其可以从单个命令或脚本执行
2.在生产系统生效的同时,可以部署软件的下一个版本,并切换到新版本,而不会降低服务质量
3.能够使用完全相同的部署到其他环境的流程来部署到生产
4.实施下面描述的最佳做法,例如金丝雀释放,回滚和监控,以提高生产系统的稳定性

把所有东西纳入版本控制

在过去通常而言我们的svn或者git当中只存在我们源代码本身,而在持续交付过程当中我们认为任何会对软件的行为,质量产生影响的部分都应该要做版本化的,并且这些任何部分的每一次变更都应该通过持续部署流水线的形式来进行自动化的验证。确保任何的变更,如代码变更,测试用例变更,环境配置变更都能得到快速的验证,以及反馈

这些相关的“变更集”包括:基础设施描述文件,源代码,测试脚本,自动化测试用例,环境配置脚本,部署脚本,以及数据库的创建,升级,以及回滚脚本等。

从上面的“变更集”也可以看出,持续交付是一个团队所有人员和角色都应该参与的事情,并且每一个人都对软件交付富有责任

提前并频繁的做让你感到痛苦的事情

“如果集成是让你感到痛苦的,那么每一次代码提交都应该进行集成,而且应该从项目一开始就开始这么做;如果发布软件过程前测试是一件痛苦的事情,那么就应该从项目一开始就不断的进行测试;如果软件发布是一件痛苦的事情,那么每一次代码提交在完成自动化验收测试之后都应该进行发布,或者至少发布到类生产环境”

内建质量

在持续交付过程中持续交付流水线定义了一套标准的,可重复的软件交付流程;同时借助大量的工具我们可以将这个流程中的机会所有事情都进行自动化。但是另外一个点就是软件质量。

根据原则四,其实我们也可以推断出如果对代码进行测试是一件痛苦的事情,那么在编写实现代码之前我们就应该写测试,TDD,ATDD,BDD等软件研发实践正是体现了这一基本原则。

内建质量是戴明提出的名言之一。越早的发现缺陷,修复它们的成本越低。

根据内建质量的原则我们可以知道在软件交付过程中,测试并不是一个阶段,所以并不应该在开发介绍之后才开始。同时测试也不应该主要是测试人员的职责,参与交付的所有人都应该对软件的质量负责

其中测试四象限很好的阐述了为了确保软件质量而应该做的各种类型的测试建模

Done”意味着“已发布”

在持续交付过程中认为一个特性的交付在理想状态下应该是已经发布到用户手中,或者至少已经向用户进行了演示。

相应的在敏捷开发中,我们每一个迭代结束后都应该想"用户代表"进行演示,并且在“用户代表”试用认为是完成了之后才意味则“Done”

其中“用户代表”可以是正在的用户,也可以是相关的业务人员

交付过程是每个成员的责任

在现实情况下,测试部门总是抱怨研发交付的软件质量差,运维总是抱怨软件不够稳定,开发总是抱怨缺陷反馈周期太长,解决问题的成本过高。

而在持续交付当中我们知道,对于交付团队而言最终目标是确保软件能够交付到用户手中,并且产生相应的价值。

而通过持续部署流水线,我们将所有参与到软件交付中的角色都联合成了一个整体,并且各个部分之间是能够快速的产生反馈,促成各个成员和角色之间的交流,并且快速的解决问题

持续改进

在任何一个充满生机的组织当中持续改进是这个组织保持活力的基本要素之一。

参与软件交付的成员需要定期对过去一段时间内的交付工作进行回顾,去发现在这个流程当中的做的好的方面,以及做的不好的方面,并且提出解决方案。

Jenkins实现Pipeline示例

gitlab配置

创建Group

创建Project

初始化Project

克隆Project源码

配置ssh私钥

Jenkins全局配置

配置Jdk

配置Maven

配置Mail

配置权限

Jenkins使用配置

配置WebHook

Pipeline实现CI/CD

项目根目录增加jenkinsfile文件jenkins通过webhook与gitlab上pull源码就行脚本流水线操作

cr:sonar

ci:build、test、package

cd:deploy、docker、harbor、kubernetes

master分支流水线脚本

if(BRANCH_NAME.startsWith("master")){

    // 克隆指定分支代码
    stage('git clone') {
        echo 'STEP 1:git clone'
        git credentialsId: '5570ab9e-7bfc-4f88-be61-b0d59d38fe8b', url: 'http://gitlab-bigdata.huan.tv/jenkins-ci-cd/spring-boot-maven.git'
    }

    // 将指定配置文件远程拷贝到指定服务器
    stage('init config template files') {
        echo 'STEP 2: 初始化配置文件目录 && 拷贝docker files、harbor file、k8s files到各自服务器'

        // 初始化配置文件目录
        sh 'scp ./devops/prod/docker-dir.sh root@172.20.10.39:/usr/local/project/'
        sh 'scp ./devops/prod/k8s-dir.sh root@172.20.10.179:/usr/local/'

        // 授权 && 执行
        sh 'ssh root@172.20.10.39  "cd /usr/local/project/;chmod 777 docker-dir.sh"'
        sh 'ssh root@172.20.10.179  "cd /usr/local/;chmod 777 k8s-dir.sh"'
        sh 'ssh root@172.20.10.39  "/usr/local/project/docker-dir.sh"'
        sh 'ssh root@172.20.10.179  "/usr/local/k8s-dir.sh"'

        // 拷贝docker files、harbor file、k8s files到各自服务器
        // docker files
        sh 'scp ./devops/prod/docker-buildimage.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-prod/'
        sh 'scp ./devops/prod/docker-delete.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-prod/'
        sh 'scp ./devops/prod/Dockerfile root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-prod/'
        sh 'scp ./devops/prod/docker-run.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-prod/'

        // harbor file
        sh 'scp ./devops/prod/harbor-runHarbor.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-prod/'

        // k8s files
        sh 'scp ./devops/prod/k8s-deployment.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-prod/'
        sh 'scp ./devops/prod/k8s-ingress.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-prod/'
        sh 'scp ./devops/prod/k8s-service.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-prod/'
        sh 'scp ./devops/prod/k8s-service_start.sh  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-prod/'

        // 授权
        sh 'ssh root@172.20.10.39  "cd /home/DevOps/dsp/ad-project/ad-project-prod/;chmod 777 *"'
        sh 'ssh root@172.20.10.179  "cd /home/DevOps/dsp/ad-project/ad-project-prod/;chmod 777 *"'
    }

   // 构建
   stage('build') {
       echo 'STEP 3: 构建'
       sh '/usr/local/apache-maven-3.5.4/bin/mvn compile'
       // sh '/usr/local/apache-maven-3.5.4/bin/mvn findbugs:check'
   }

    // jar/war 生成
    stage('package') {
        echo 'STEP 4:打包'
        withEnv(['JAVA_HOME=/usr/java/jdk1.8.0_192-amd64']) {
            sh '/usr/local/apache-maven-3.5.4/bin/mvn package -Dmaven.test.skip=true'
            println env.BUILD_NUMBER
        }
        input '是否确定上线'
    }

    // 执行Dockerfile生成镜像
    stage('build docker image') {
        echo 'STEP 5:执行Dockerfile生成镜像'
        sh 'scp /var/lib/jenkins/workspace/spring-boot-maven-master/target/spring-boot-maven-0.0.1-SNAPSHOT.jar root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-prod/'
        sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-prod/docker-delete.sh"'
        sh 'ssh -t root@172.20.10.39 -p 22 "cd /home/DevOps/dsp/ad-project/ad-project-prod && ./docker-buildimage.sh; bash"'
        sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-prod/docker-run.sh"'
    }

    // 推送Docker镜像到Harbor容器
    stage('push image to harbor') {
        echo 'STEP 6:推送Docker镜像到Harbor容器'
        sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-prod/harbor-runHarbor.sh"'
    }

    // 部署k8s应用 && 发送邮件(可选)
    stage('deploy to kubernetes') {
        echo 'STEP 7:推送Docker镜像到Harbor容器'
        try {
            sh 'ssh root@172.20.10.179 "/home/DevOps/dsp/ad-project/ad-project-prod/k8s-service_start.sh"'
            currentBuild.result = 'SUCCESS'
        } catch (any) {
            currentBuild.result = 'FAILURE'
            throw any
        } finally {
            if (currentBuild.result == 'FAILURE') {
                emailext (
                    subject: "build FAILURE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
                    to: "yuanbu@huan.tv,xuelei@huan.tv,malei@huan.tv",
                    body: """<p>FAILURE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':</p><p>Check console output at &QUOT;<a href='${env.BUILD_URL}'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>&QUOT;</p>""",recipientProviders: [[$class:'DevelopersRecipientProvider']]
                )
            } else if (currentBuild.result == 'SUCCESS') {
                emailext (
                    subject: "build SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
                    to: "yuanbu@huan.tv,xuelei@huan.tv,malei@huan.tv",
                    body: """<p>SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':</p><p>Check console output at &QUOT;<a href='${env.BUILD_URL}'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>&QUOT;</p>""",recipientProviders: [[$class:'DevelopersRecipientProvider']]
                )
            }
        }
    }

test分支流水线脚本

if (BRANCH_NAME.startsWith("test")) {
        // test
      // 克隆指定分支代码
      stage('git clone') {
          echo 'STEP 1:git clone'
          git branch: 'test', credentialsId: '55ee8e5b-053f-44e9-8391-ac6cd121ef6b', url: 'http://gitlab-bigdata.huan.tv/jenkins-ci-cd/spring-boot-maven.git'
      }

      // 将指定配置文件远程拷贝到指定服务器
      stage('init config template files') {
          echo 'STEP 2: 初始化配置文件目录 && 拷贝docker files、harbor file、k8s files到各自服务器'

          // 初始化配置文件目录
          sh 'scp ./devops/test/docker-dir.sh root@172.20.10.39:/usr/local/project/'
          sh 'scp ./devops/test/k8s-dir.sh root@172.20.10.179:/usr/local/'

          // 授权 && 执行
          sh 'ssh root@172.20.10.39  "cd /usr/local/project/;chmod 777 docker-dir.sh"'
          sh 'ssh root@172.20.10.179  "cd /usr/local/;chmod 777 k8s-dir.sh"'
          sh 'ssh root@172.20.10.39  "/usr/local/project/docker-dir.sh"'
          sh 'ssh root@172.20.10.179  "/usr/local/k8s-dir.sh"'

          // 拷贝docker files、harbor file、k8s files到各自服务器
          // docker files
          sh 'scp ./devops/test/docker-buildimage.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-test/'
          sh 'scp ./devops/test/docker-delete.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-test/'
          sh 'scp ./devops/test/Dockerfile root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-test/'
          sh 'scp ./devops/test/docker-run.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-test/'

          // harbor file
          sh 'scp ./devops/test/harbor-runHarbor.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-test/'

          // k8s files
          sh 'scp ./devops/test/k8s-deployment.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-test/'
          sh 'scp ./devops/test/k8s-ingress.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-test/'
          sh 'scp ./devops/test/k8s-service_start.sh  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-test/'
          sh 'scp ./devops/test/k8s-service.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-test/'

          // 授权
          sh 'ssh root@172.20.10.39  "cd /home/DevOps/dsp/ad-project/ad-project-test/;chmod 777 *"'
          sh 'ssh root@172.20.10.179  "cd /home/DevOps/dsp/ad-project/ad-project-test/;chmod 777 *"'
      }

      // 代码审查
      stage('sonar code review') {
          echo 'STEP 3: sonar 代码审查'
          withSonarQubeEnv('SonarQubeServer') {
              sh '/usr/local/sonar-scanner-3.3.0.1492-linux/bin/sonar-scanner'
          }
      }

     // 构建
     stage('build') {
         echo 'STEP 4: 构建'
         sh '/usr/local/apache-maven-3.5.4/bin/mvn compile'
         // sh '/usr/local/apache-maven-3.5.4/bin/mvn findbugs:check'
     }

      // 单元测试
      stage('test') {
           echo 'STEP 5: 单元测试'
           sh '/usr/local/apache-maven-3.5.4/bin/mvn test'
      }

      // jar/war 生成
      stage('package') {
          echo 'STEP 6:打包'
          withEnv(['JAVA_HOME=/usr/java/jdk1.8.0_192-amd64']) {
              sh '/usr/local/apache-maven-3.5.4/bin/mvn package -Dmaven.test.skip=true'
              println env.BUILD_NUMBER
          }
      }

      // 执行Dockerfile生成镜像
      stage('build docker image') {
          echo 'STEP 7:执行Dockerfile生成镜像'
          sh 'scp /var/lib/jenkins/workspace/spring-boot-maven-test/target/spring-boot-maven-0.0.1-SNAPSHOT.jar root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-test/'
          sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-test/docker-delete.sh"'
          sh 'ssh -t root@172.20.10.39 -p 22 "cd /home/DevOps/dsp/ad-project/ad-project-test && ./docker-buildimage.sh; bash"'
          sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-test/docker-run.sh"'
      }

      // 推送Docker镜像到Harbor容器
      stage('push image to harbor') {
          echo 'STEP 8:推送Docker镜像到Harbor容器'
          sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-test/harbor-runHarbor.sh"'
      }

      // 部署k8s应用 && 发送邮件(可选)
      stage('deploy to kubernetes') {
          echo 'STEP 9:推送Docker镜像到Harbor容器'
          try {
              sh 'ssh root@172.20.10.179 "/home/DevOps/dsp/ad-project/ad-project-test/k8s-service_start.sh"'
              currentBuild.result = 'SUCCESS'
          } catch (any) {
              currentBuild.result = 'FAILURE'
              throw any
          } finally {
              if (currentBuild.result == 'FAILURE') {
                  emailext (
                      subject: "build FAILURE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
                      to: "yuanbu@huan.tv,xuelei@huan.tv,malei@huan.tv",
                      body: """<p>FAILURE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':</p><p>Check console output at &QUOT;<a href='${env.BUILD_URL}'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>&QUOT;</p>""",recipientProviders: [[$class:'DevelopersRecipientProvider']]
                  )
              } else if (currentBuild.result == 'SUCCESS') {
                  emailext (
                      subject: "build SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
                      to: "yuanbu@huan.tv,xuelei@huan.tv,malei@huan.tv",
                      body: """<p>SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':</p><p>Check console output at &QUOT;<a href='${env.BUILD_URL}'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>&QUOT;</p>""",recipientProviders: [[$class:'DevelopersRecipientProvider']]
                  )
              }
          }

      }

}

develop分支流水线脚本

if(BRANCH_NAME.startsWith("develop")){

     // 克隆指定分支代码
     stage('git clone') {
         echo 'STEP 1:git clone'
         git branch: 'develop', credentialsId: '55ee8e5b-053f-44e9-8391-ac6cd121ef6b', url: 'http://gitlab-bigdata.huan.tv/jenkins-ci-cd/spring-boot-maven.git'
     }

     // 将指定配置文件远程拷贝到指定服务器
     stage('init config template files') {
         echo 'STEP 2: 初始化配置文件目录 && 拷贝docker files、harbor file、k8s files到各自服务器'

         // 初始化配置文件目录
         sh 'scp ./devops/develop/docker-dir.sh root@172.20.10.39:/usr/local/project/'
         sh 'scp ./devops/develop/k8s-dir.sh root@172.20.10.179:/usr/local/'

         // 授权 && 执行
         sh 'ssh root@172.20.10.39  "cd /usr/local/project/;chmod 777 docker-dir.sh"'
         sh 'ssh root@172.20.10.179  "cd /usr/local/;chmod 777 k8s-dir.sh"'
         sh 'ssh root@172.20.10.39  "/usr/local/project/docker-dir.sh"'
         sh 'ssh root@172.20.10.179  "/usr/local/k8s-dir.sh"'

         // 拷贝docker files、harbor file、k8s files到各自服务器
         // docker files
         sh 'scp ./devops/develop/docker-buildimage.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-develop/'
         sh 'scp ./devops/develop/docker-delete.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-develop/'
         sh 'scp ./devops/develop/Dockerfile root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-develop/'
         sh 'scp ./devops/develop/docker-run.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-develop/'

         // harbor file
         sh 'scp ./devops/develop/harbor-runHarbor.sh root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-develop/'

         // k8s files
         sh 'scp ./devops/develop/k8s-deployment.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-develop/'
         sh 'scp ./devops/develop/k8s-ingress.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-develop/'
         sh 'scp ./devops/develop/k8s-service_start.sh  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-develop/'
         sh 'scp ./devops/develop/k8s-service.yaml  root@172.20.10.179:/home/DevOps/dsp/ad-project/ad-project-develop/'

         // 授权
         sh 'ssh root@172.20.10.39  "cd /home/DevOps/dsp/ad-project/ad-project-develop/;chmod 777 *"'
         sh 'ssh root@172.20.10.179  "cd /home/DevOps/dsp/ad-project/ad-project-develop/;chmod 777 *"'
     }

     // 代码审查
     stage('sonar code review') {
         echo 'STEP 3: sonar 代码审查'
         withSonarQubeEnv('SonarQubeServer') {
             sh '/usr/local/sonar-scanner-3.3.0.1492-linux/bin/sonar-scanner'
         }
     }

    // 构建
    stage('build') {
        echo 'STEP 4: 构建'
        sh '/usr/local/apache-maven-3.5.4/bin/mvn compile'
        // sh '/usr/local/apache-maven-3.5.4/bin/mvn findbugs:check'
    }

     // 单元测试
     stage('test') {
          echo 'STEP 5: 单元测试'
          sh '/usr/local/apache-maven-3.5.4/bin/mvn test'
     }

     // jar/war 生成
     stage('package') {
         echo 'STEP 6:打包'
         withEnv(['JAVA_HOME=/usr/java/jdk1.8.0_192-amd64']) {
             sh '/usr/local/apache-maven-3.5.4/bin/mvn package -Dmaven.test.skip=true'
             println env.BUILD_NUMBER
         }
     }

     // 执行Dockerfile生成镜像
     stage('build docker image') {
         echo 'STEP 7:执行Dockerfile生成镜像'
         sh 'scp /var/lib/jenkins/workspace/spring-boot-maven-test/target/spring-boot-maven-0.0.1-SNAPSHOT.jar root@172.20.10.39:/home/DevOps/dsp/ad-project/ad-project-develop/'
         sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-develop/docker-delete.sh"'
         sh 'ssh -t root@172.20.10.39 -p 22 "cd /home/DevOps/dsp/ad-project/ad-project-develop && ./docker-buildimage.sh; bash"'
         sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-develop/docker-run.sh"'
     }

     // 推送Docker镜像到Harbor容器
     stage('push image to harbor') {
         echo 'STEP 8:推送Docker镜像到Harbor容器'
         sh 'ssh root@172.20.10.39  "/home/DevOps/dsp/ad-project/ad-project-develop/harbor-runHarbor.sh"'
     }

     // 部署k8s应用 && 发送邮件(可选)
     stage('deploy to kubernetes') {
         echo 'STEP 9:推送Docker镜像到Harbor容器'
         try {
             sh 'ssh root@172.20.10.179 "/home/DevOps/dsp/ad-project/ad-project-develop/k8s-service_start.sh"'
             currentBuild.result = 'SUCCESS'
         } catch (any) {
             currentBuild.result = 'FAILURE'
             throw any
         } finally {
             if (currentBuild.result == 'FAILURE') {
                 emailext (
                     subject: "build FAILURE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
                     to: "yuanbu@huan.tv,xuelei@huan.tv,malei@huan.tv",
                     body: """<p>FAILURE: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':</p><p>Check console output at &QUOT;<a href='${env.BUILD_URL}'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>&QUOT;</p>""",recipientProviders: [[$class:'DevelopersRecipientProvider']]
                 )
             } else if (currentBuild.result == 'SUCCESS') {
                 emailext (
                     subject: "build SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
                     to: "yuanbu@huan.tv,xuelei@huan.tv,malei@huan.tv",
                     body: """<p>SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':</p><p>Check console output at &QUOT;<a href='${env.BUILD_URL}'>${env.JOB_NAME} [${env.BUILD_NUMBER}]</a>&QUOT;</p>""",recipientProviders: [[$class:'DevelopersRecipientProvider']]
                 )
             }
         }
     }
} 

DevOps持续交付小结

DevOps的目的是更快速,更可靠地创建质量更好的软件,同时开发,运维团队之间进行更多的沟通和协作。 它是一个自动化过程,允许快速,安全和高质量的软件开发和发布,同时保持所有利益相关者在一个循环中。 这就是DevOps获得越来越多的大型互联网公司青睐的真正原因。

DevOps案例研究

曾经,Facebook向遍布全球的若干亿用户推出了一系列新功能 - 时间轴,推荐和音乐功能。 发布后Facebook上产生的巨大流量导致服务器崩溃。 推出的功能获得了用户的大规模超常规响应,这导致了新功能产生了不可控的结果,使他们没有预料到。

这导致了Facebook重新评估和战略调整,从而使Facebook推出了暗启动技术。 使用DevOps原则,Facebook为其新版本的发布创建了以下方法。

Facebook暗启动技术

暗启动是在新功能完全发布给所有用户之前,逐步将新功能,推广到选定的一组用户的过程。 这允许开发团队尽早获得用户反馈,测试错误,并且还可以测试基础架构性能。 这种发布方法是持续交付的直接结果,有助于实现更快,更迭代的版本,确保应用程序性能不会受到影响,并且用户可以很好地更新该版本。

在暗启动技术中,新功能通过专用的部署管道发布给小型用户群。 在上面给出的Facebook暗启动图表中,您可以看到只打开了一个部署管道,将新功能部署到一组选定用户。 此时剩余的数百条管道全部关闭。

持续监视部署功能的特定用户群,以收集反馈并识别错误。 这些错误和反馈将被纳入开发,测试和部署在同一用户群中,直到功能变得稳定。 一旦实现稳定性,通过启用其他部署管道,将逐步在其他用户群上部署这些功能。

Facebook通过将代码包装在功能标记或功能切换中来实现此目的,该切换用于控制谁可以看到新功能以及何时查看。与此同时,模拟向用户启动代码的全部效果,在向用户开放全部功能之前,可以及早的暴露应用程序基础架构的痛点和区域,功能稳定后,将通过多个版本将其部署到其余用户。

通过这种方式,Facebook拥有一个受控或稳定的机制,可以为其庞大的用户群开发新功能。相反,如果功能没有得到很好的响应,他们可以选择完全回滚部署。这也帮助他们为部署准备服务器,因为他们可以预测网站上的用户活动,并相应地扩展服务器。上面给出的图表描述了Facebook的暗启动过程。