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