DevOps定义(来自维基百科): DevOps(Development和Operations的组合词)是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合做的文化、运动或惯例。透过自动化“软件交付”和“架构变动”的流程,来使得构建、测试、发布软件可以更加地快捷、频繁和可靠。html
公司技术部目前几百人左右吧,可是整个技术栈仍是比较落后的,尤为是DevOps、容器这一块,须要将全线打通,当时进来也主要是负责DevOps这一块的工做,应该说也是没怎么作好,其中也走了很多弯路,下面主要是本身踩过的坑吧。java
主要仍是基于jenkins里面构建一个自由风格的软件项目,当时参考的是阿里的codepipeline,就是对jenkins封装一层,包括建立job、当即构建、获取构建进度等都进行封装,并将须要的东西进行存库,没有想到码代码的时候,一堆的坑,好比: 1.连续点击当即构建,jenkins是不按顺序返回的,(分布式锁解决) 2.跨域调用,csrf,这个还好,不过容易把jenkins搞的没法登陆(注意配置,具体能够点击这里) 3.建立job的时候只支持xml格式,还要转换一下,超级坑(xstream强行转换) 4.docker构建的时候,须要挂载宿主机的docker(想过用远程的,但效率不高) 5.数据库与jenkins的job一致性问题,任务建立失败,批量删除太慢(目前没想好怎么解决) 6.因为使用了数据库,须要检测job是否构建完成,为了自定义参数,咱们自写了个通知插件,将构建状态返回到kafka,而后管理平台在进行消息处理。git
完成了以上的东西,不过因为太过于简单,致使只能进行单条线的CICD,并且CI仅仅实现了打包,没有将CD的过程一同串行起来。简单来讲就是,用户点击了构建只是可以打出一个镜像,可是若是要部署到kubernetes,仍是须要在应用里手动更换一下镜像版本。整体而言,这个版本的jenkins咱们使用的仍是单点的,不足以支撑构建量比较大的状况,甚至若是当前服务挂了,断网了,整一块的构建功能都不能用。github
<project> <actions/> <description>xxx</description> <properties> <hudson.model.ParametersDefinitionProperty> <parameterDefinitions> <hudson.model.TextParameterDefinition> <name>buildParam</name> <defaultValue>v1</defaultValue> </hudson.model.TextParameterDefinition> <hudson.model.TextParameterDefinition> <name>codeBranch</name> <defaultValue>master</defaultValue> </hudson.model.TextParameterDefinition> </parameterDefinitions> </hudson.model.ParametersDefinitionProperty> </properties> <scm class="hudson.plugins.git.GitSCM"> <configVersion>2</configVersion> <userRemoteConfigs> <hudson.plugins.git.UserRemoteConfig> <url>http://xxxxx.git</url> <credentialsId>002367566a4eb4bb016a4eb723550054</credentialsId> </hudson.plugins.git.UserRemoteConfig> </userRemoteConfigs> <branches> <hudson.plugins.git.BranchSpec> <name>${codeBranch}</name> </hudson.plugins.git.BranchSpec> </branches> <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations> <extensions/> </scm> <builders> <hudson.tasks.Shell> <command>ls</command> </hudson.tasks.Shell> <hudson.tasks.Maven> <targets>clean package install -Dmaven.test.skip=true</targets> <mavenName>mvn3.5.4</mavenName> </hudson.tasks.Maven> <com.cloudbees.dockerpublish.DockerBuilder> <server> <uri>unix:///var/run/docker.sock</uri> </server> <registry> <url>http://xxxx</url> </registry> <repoName>xxx/xx</repoName> <forcePull>true</forcePull> <dockerfilePath>Dockerfile</dockerfilePath> <repoTag>${buildParam}</repoTag> <skipTagLatest>true</skipTagLatest> </com.cloudbees.dockerpublish.DockerBuilder> </builders> <publishers> <com.xxxx.notifications.Notifier/> </publishers> </project>
上面的过程也仍然没有没住DevOps的流程,人工干预的东西依旧不少,因为上级急需产出,因此只能将就着继续下去。咱们将构建、部署每一个当作一个小块,一个CICD的过程能够选择构建、部署,花了很大的精力,完成了串行化的别样的CICD。 如下图为例,整个流程的底层为:paas平台-jenkins-kakfa-管理平台(选择cicd的下一步)-kafka-cicd组件调用管理平台触发构建-jenkins-kafka-管理平台(选择cicd的下一步)-kafka-cicd组件调用管理平台触发部署。spring
目前实现了串行化的CICD构建部署,以后考虑实现多个CICD并行,而且一个CICD可以调用另外一个CICD,实际运行中,出现了一大堆问题。因为通过的组件太多,一次cicd的运行报错,却很难排查到问题出现的缘由,业务方的投诉也开始慢慢多了起来,只能说劝导他们不要用这个功能。docker
没有CICD,就没法帮助公司上容器云,没法合理的利用容器云的特性,更没法走上云原生的道路。因而,咱们决定另谋出路。数据库
因为以前的CICD问题太多,特别是通过的组件太多了,致使出现问题的时候没法正常排查,为了可以更加稳定可靠,仍是决定了要更换一下底层。 咱们从新审视了下pipeline,以为这才是正确的作法,惋惜不知道若是作成一个产品样子的东西,用户方Dockerfile都不怎么会写,你让他写一个Jenkinsfile?不合理!在此以外,咱们看到了serverless jenkins、谷歌的tekton。 GitLab-CICD Gitlab中自带了cicd的工具,须要配置一下runner,而后配置一下.gitlab-ci.yml写一下程序的cicd过程便可,构建镜像的时候咱们使用的是kaniko,整个gitlab的cicd在咱们公司小项目中大范围使用,可是学习成本太高,尤为是引入了kaniko以后,仍是寻找一个产品化的CICD方案。json
分布式构建jenkins x 首先要解决的是多个构建同时运行的问题,好久以前就调研过jenkins x,它必需要使用在kubernetes上,因为当时官方文档不全,并且咱们的DevOps项目处于初始期,全部没有使用。jenkins的master slave结构就很少说了。jenkins x应该说是个全家桶,包含了helm仓库、nexus仓库、docker registry等,代码是jenkins-x-image。跨域
serverless jenkins 好像跟谷歌的tekton相关,用了下,没调通,只能用于GitHub。感受还不如直接使用tekton。多线程
阿里云云效 提供了图形化配置DevOps流程,支持定时触发,惋惜没有跟gitlab触发结合,若是须要个公司级的DevOps,须要将公司的jira、gitlab、jenkins等集合起来,可是图形化jenkins pipeline是个特别好的参考方向,能够结合阿里云云效来作一个本身的DevOps产品。
微软Pipeline 微软也是提供了DevOps解决方案的,也是提供了yaml格式的写法,即:在右边填写完以后会转化成yaml。若是想把DevOps打形成一款产品,这样的设计显然不是最好的。
谷歌tekton kubernetes的官方cicd,目前已用于kubernetes的release发版过程,目前也仅仅是与GitHub相结合,gitlab没法使用,全过程可以使用yaml文件来建立,跑起来就是相似kubernetes的job同样,用完即销毁,惋惜目前比较新,依旧处于alpha版本,没法用于生产。有兴趣能够参考下:Knative 初体验:CICD 极速入门
在调研DockOne以及各个产商的DevOps产品时,发现,真的只有阿里云的云效才是真正比较完美的DevOps产品,用户不须要知道pipeline的语法,也不须要掌握kubernetes的相关知识,甚至不用写yaml文件,对于开发、测试来讲简直就是神同样的存在了。云效对小公司(创业公司)免费,可是有必定的量以后,就要开始收费了。在调研了一番云效的东西以后,发现云效也是基于jenkins x改造的,不过阿里毕竟人多,虽然能约莫看出是pipeline的语法,可是阿里完全改形成了可以使用yaml来与后台交互。 下面是以阿里云的云效界面以及配合jenkins的pipeline语法来说解:
PMD是一款可拓展的静态代码分析器它不只能够对代码分析器,它不只能够对代码风格进行检查,还能够检查设计、多线程、性能等方面的问题。阿里云的是简单的集成了一下而已,对于咱们来讲,底层使用了sonar来接入,全部的代码扫描结果都接入了sonar。
stage('Clone') { steps{ git branch: 'master', credentialsId: 'xxxx', url: "xxx" } } stage('check') { steps{ container('maven') { echo "mvn pmd:pmd" } } }
Java的单元测试通常用的是Junit,在阿里云中,使用了surefire插件,用来在maven构建生命周期的test phase执行一个应用的单元测试。它会产生两种不一样形式的测试结果报告。我这里就简单的过一下,使用"mvn test"命令来代替。
stage('Clone') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxx', url: "xxxxxx" } } stage('test') { steps{ container('maven') { sh "mvn test" } } }
镜像的构建比较想使用kaniko,尝试找了很多方法,到最后仍是只能使用dind(docker in docker),挂载宿主机的docker来进行构建,若是能有其余方案,但愿能提醒下。目前jenkins x使用的是dind,挂载的时候须要配置一下config.json,而后挂载到容器的/root/.docker目录,才能在容器中使用docker。
为何不推荐dind:挂载了宿主机的docker,就可使用docker ps查看正在运行的容器,也就意味着可使用docker stop、docker rm来控制宿主机的容器,虽然kubernetes会从新调度起来,可是这一段的重启时间极大的影响业务。
stage('下载代码') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxx', url: "xxxxxx" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } } } stage('打包并构建镜像') { steps{ container('maven') { echo "3.Build Docker Image Stage" sh "mvn clean install -Dmaven.test.skip=true" sh "docker build -f xxx/Dockerfile -t xxxxxx:${build_tag} ." sh "docker push xxxxxx:${build_tag}" } } }
CD过程有点困难,因为咱们的kubernetes平台是图形化的,相似于阿里云,用户根本不须要本身写deployment,只须要在图形化界面作一下操做便可部署。对于CD过程来讲,若是应用存在的话,就能够直接替换掉镜像版本便可,若是没有应用,就提供个简单的界面让用户新建应用。固然,在容器最初推行的时候,对于用户来讲,一会儿须要接受docker、kubernetes、helm等概念是十分困难的,不能一个一个帮他们写deployment这些yaml文件,只能用helm建立一个通用的spring boot或者其余的模板,而后让业务方修改本身的配置,每次构建的时候只须要替换镜像便可。
def tmp = sh ( returnStdout: true, script: "kubectl get deployment -n ${namespace} | grep ${JOB_NAME} | awk '{print \$1}'" ) //若是是第一次,则使用helm模板建立,建立完后须要去epaas修改pod的配置 if(tmp.equals('')){ sh "helm init --client-only" sh """helm repo add mychartmuseum http://xxxxxx \ --username myuser \ --password=mypass""" sh """helm install --set name=${JOB_NAME} \ --set namespace=${namespace} \ --set deployment.image=${image} \ --set deployment.imagePullSecrets=${harborProject} \ --name ${namespace}-${JOB_NAME} \ mychartmuseum/soa-template""" }else{ println "已经存在,替换镜像" //epaas中一个pod的容器名称须要带上"-0"来区分 sh "kubectl set image deployment/${JOB_NAME} ${JOB_NAME}-0=${image} -n ${namespace}" }
代码扫描,单元测试,构建镜像三个并行运行,等三个完成以后,在进行部署
pipeline:
pipeline { agent { label "jenkins-maven" } stages{ stage('代码扫描,单元测试,镜像构建'){ parallel { stage('并行任务一') { agent { label "jenkins-maven" } stages('Java代码扫描') { stage('Clone') { steps{ git branch: 'master', credentialsId: 'xxxxxxx', url: "xxxxxxx" } } stage('check') { steps{ container('maven') { echo "$BUILD_NUMBER" } } } } } stage('并行任务二') { agent { label "jenkins-maven" } stages('Java单元测试') { stage('Clone') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxxxx', url: "xxxxxxx" } } stage('test') { steps{ container('maven') { echo "3.Build Docker Image Stage" sh "mvn -v" } } } } } stage('并行任务三') { agent { label "jenkins-maven" } stages('java构建镜像') { stage('Clone') { steps{ echo "1.Clone Stage" git branch: 'master', credentialsId: 'xxxxxxx', url: "xxxxxxx" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } } } stage('Build') { steps{ container('maven') { echo "3.Build Docker Image Stage" sh "mvn clean install -Dmaven.test.skip=true" sh "docker build -f epaas-portal/Dockerfile -t hub.gcloud.lab/rongqiyun/epaas:${build_tag} ." sh "docker push hub.gcloud.lab/rongqiyun/epaas:${build_tag}" } } } } } } } stage('部署'){ stages('部署到容器云') { stage('check') { steps{ container('maven') { script{ if (deploy_app == "true"){ def tmp = sh ( returnStdout: true, script: "kubectl get deployment -n ${namespace} | grep ${JOB_NAME} | awk '{print \$1}'" ) //若是是第一次,则使用helm模板建立,建立完后须要去epaas修改pod的配置 if(tmp.equals('')){ sh "helm init --client-only" sh """helm repo add mychartmuseum http://xxxxxx \ --username myuser \ --password=mypass""" sh """helm install --set name=${JOB_NAME} \ --set namespace=${namespace} \ --set deployment.image=${image} \ --set deployment.imagePullSecrets=${harborProject} \ --name ${namespace}-${JOB_NAME} \ mychartmuseum/soa-template""" }else{ println "已经存在,替换镜像" //epaas中一个pod的容器名称须要带上"-0"来区分 sh "kubectl set image deployment/${JOB_NAME} ${JOB_NAME}-0=${image} -n ${namespace}" } }else{ println "用户选择不部署代码" } } } } } } } } }
在jenkins x中查看:
jenkins blue ocean步骤日志:
云效中的日志:
triggers { cron('H H * * *') //天天 }
pipeline中除了有对于时间的trigger,还支持了gitlab的触发,须要各类配置,不过若是真的对于gitlab的cicd有要求,直接使用gitlab-ci会更好,咱们同时也对gitlab进行了runner的配置来支持gitlab的cicd。gitlab的cicd也提供了构建完后即销毁的过程。
功能最强大的过程莫过于本身使用pipeline脚本实现,选取最适合本身的,可是对于一个公司来讲,若是要求业务方来掌握这些,特别是IT流动性大的时候,既须要从新培训,同个问题又会被问多遍,因此,只能将DevOps实现成一个图形化的东西,方便,简单,相对来讲功能还算强大。
DevOps最难的可能都不是以上这些,关键是让用户接受,容器云最初推行时,公司本来传统的不少发版方式都须要进行改变,有些业务方不肯意改,或者有些代码把持久化的东西存到了代码中而不是分布式存储里,甚至有些用户方都不肯意维护老代码,看都不想看而后想上容器,一个公司在作技术架构的时候,过于混乱到最后填坑要么须要耗费太多精力甚至大换血。
最后,DevOps是云原生的必经之路!!!
文章同步:
博客园:http://www.javashuo.com/article/p-dfkpmfsj-dn.html
我的网站:http://www.wenzhihuai.com/getblogdetail.html?blogid=663
gitbook:https://gitbook.wenzhihuai.com/devops/devops-ping-tai